mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-16 22:30:42 +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();
|
||||
}
|
||||
|
||||
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) {
|
||||
// mix across slave threads
|
||||
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,
|
||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||
bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame;
|
||||
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
||||
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
||||
if (overBudget) {
|
||||
if (PALIsOpen) {
|
||||
_stats.overBudgetAvatars++;
|
||||
|
@ -497,8 +498,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
_stats.avatarDataPackingElapsedTime +=
|
||||
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||
if (!overBudget) {
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||
}
|
||||
|
||||
remainingAvatars--;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,85 @@ $(document).ready(function(){
|
|||
function progressBarHTML(extraClass, label) {
|
||||
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 += label + "<span class='sr-only'></span></div></div>";
|
||||
html += "<span class='ongoing-msg'></span></div></div>";
|
||||
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() {
|
||||
// construct the HTML needed for the settings backup panel
|
||||
var html = "<div class='form-group'><div id='" + UPLOAD_CONTENT_ALLOWED_DIV_ID + "'>";
|
||||
|
@ -50,34 +125,10 @@ $(document).ready(function(){
|
|||
"Restore content",
|
||||
function() {
|
||||
var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files');
|
||||
var file = files[0];
|
||||
|
||||
var fileFormData = new FormData();
|
||||
fileFormData.append('restore-file', files[0]);
|
||||
|
||||
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."
|
||||
);
|
||||
});
|
||||
showUploadProgress("Uploading " + file.name);
|
||||
uploadNextChunk(file);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -168,6 +219,11 @@ $(document).ready(function(){
|
|||
checkBackupStatus();
|
||||
});
|
||||
|
||||
function updateProgressBars($progressBar, value) {
|
||||
$progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%');
|
||||
$progressBar.find('.ongoing-msg').html(" " + Math.round(value) + "%");
|
||||
}
|
||||
|
||||
function reloadBackupInformation() {
|
||||
// make a GET request to get backup information to populate the table
|
||||
$.ajax({
|
||||
|
@ -204,11 +260,6 @@ $(document).ready(function(){
|
|||
+ "<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
|
||||
// remove our flag for active rows
|
||||
$('.' + 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() {
|
||||
|
||||
QDir backupDir { _backupDirectory };
|
||||
|
|
|
@ -86,6 +86,7 @@ public slots:
|
|||
void createManualBackup(MiniPromise::Promise promise, const QString& name);
|
||||
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
|
||||
void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename);
|
||||
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||
|
||||
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
|
||||
// to make sure we handle this filetype for a content restore
|
||||
auto dispositionValue = QString(firstFormData.first.value("Content-Disposition"));
|
||||
auto formDataFilenameRegex = QRegExp("filename=\"(.+)\"");
|
||||
auto matchIndex = formDataFilenameRegex.indexIn(dispositionValue);
|
||||
QRegExp formDataFieldsRegex(R":(name="(restore-file.*)".*filename="(.+)"):");
|
||||
auto matchIndex = formDataFieldsRegex.indexIn(dispositionValue);
|
||||
|
||||
QString formItemName = "";
|
||||
QString uploadedFilename = "";
|
||||
if (matchIndex != -1) {
|
||||
uploadedFilename = formDataFilenameRegex.cap(1);
|
||||
}
|
||||
|
||||
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);
|
||||
formItemName = formDataFieldsRegex.cap(1);
|
||||
uploadedFilename = formDataFieldsRegex.cap(2);
|
||||
}
|
||||
|
||||
// Received a chunk
|
||||
processPendingContent(connection, formItemName, uploadedFilename, firstFormData.second);
|
||||
} else {
|
||||
// respond with a 400 for failure
|
||||
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) {
|
||||
// grab the UUID state property from the reply
|
||||
QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QHostAddress>
|
||||
#include <QAbstractNativeEventFilter>
|
||||
|
||||
#include <Assignment.h>
|
||||
|
@ -209,6 +210,8 @@ private:
|
|||
|
||||
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
|
||||
|
||||
bool processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk);
|
||||
|
||||
bool forwardMetaverseAPIRequest(HTTPConnection* connection,
|
||||
const QString& metaversePath,
|
||||
const QString& requestSubobject,
|
||||
|
@ -281,6 +284,9 @@ private:
|
|||
|
||||
QHash<QUuid, QPointer<HTTPSConnection>> _pendingOAuthConnections;
|
||||
|
||||
std::unordered_map<int, QByteArray> _pendingUploadedContents;
|
||||
std::unordered_map<int, std::unique_ptr<QTemporaryFile>> _pendingContentFiles;
|
||||
|
||||
QThread _assetClientThread;
|
||||
};
|
||||
|
||||
|
|
|
@ -701,9 +701,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.53203323516845703,
|
||||
"x": -0.59333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07286686894893646
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||
"texture": {
|
||||
|
@ -752,7 +752,7 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.59333323516845703,
|
||||
"x": -0.65333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
|
@ -777,9 +777,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.5103323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.127054843521118164
|
||||
"x": -0.5503323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07282185554504395
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||
"texture": {
|
||||
|
@ -1479,9 +1479,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.53203323516845703,
|
||||
"x": -0.59333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07286686894893646
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||
"texture": {
|
||||
|
@ -1530,7 +1530,7 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.59333323516845703,
|
||||
"x": -0.65333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
|
@ -1555,9 +1555,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.5103323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.127054843521118164
|
||||
"x": -0.5503323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07282185554504395
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||
"texture": {
|
||||
|
@ -2305,9 +2305,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.53203323516845703,
|
||||
"x": -0.59333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07286686894893646
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||
"texture": {
|
||||
|
@ -2356,7 +2356,7 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.59333323516845703,
|
||||
"x": -0.65333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
|
@ -2381,9 +2381,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.5103323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.127054843521118164
|
||||
"x": -0.5503323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07282185554504395
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||
"texture": {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
window.isKeyboardRaised = false;
|
||||
window.isNumericKeyboard = false;
|
||||
window.isPasswordField = false;
|
||||
window.lastActiveElement = null;
|
||||
window.lastActiveInputElement = null;
|
||||
|
||||
function getActiveElement() {
|
||||
return document.activeElement;
|
||||
|
@ -70,11 +70,15 @@
|
|||
var keyboardRaised = shouldRaiseKeyboard();
|
||||
var numericKeyboard = shouldSetNumeric();
|
||||
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 &&
|
||||
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
|
||||
|| passwordField !== window.isPasswordField || activeElement !== window.lastActiveElement)) {
|
||||
|| passwordField !== window.isPasswordField || activeInputElement !== window.lastActiveInputElement)) {
|
||||
|
||||
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
|
||||
EventBridge.emitWebEvent(
|
||||
|
@ -96,7 +100,7 @@
|
|||
window.isKeyboardRaised = keyboardRaised;
|
||||
window.isNumericKeyboard = numericKeyboard;
|
||||
window.isPasswordField = passwordField;
|
||||
window.lastActiveElement = activeElement;
|
||||
window.lastActiveInputElement = activeInputElement;
|
||||
}
|
||||
}, POLL_FREQUENCY);
|
||||
|
||||
|
|
Binary file not shown.
|
@ -141,6 +141,7 @@ TabletModalWindow {
|
|||
|
||||
Component.onDestruction: {
|
||||
loginKeyboard.raised = false;
|
||||
KeyboardScriptingInterface.raised = false;
|
||||
}
|
||||
|
||||
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/WindowScriptingInterface.h"
|
||||
#include "scripting/SelectionScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "DependencyManager.h"
|
||||
|
||||
#include "raypick/StylusPointer.h"
|
||||
|
@ -54,9 +55,9 @@
|
|||
static const int LEFT_HAND_CONTROLLER_INDEX = 0;
|
||||
static const int RIGHT_HAND_CONTROLLER_INDEX = 1;
|
||||
|
||||
static const float MALLET_LENGTH = 0.2f;
|
||||
static const float MALLET_TOUCH_Y_OFFSET = 0.052f;
|
||||
static const float MALLET_Y_OFFSET = 0.180f;
|
||||
static const float MALLET_LENGTH = 0.18f;
|
||||
static const float MALLET_TOUCH_Y_OFFSET = 0.050f;
|
||||
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::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 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_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_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f};
|
||||
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 float PULSE_STRENGTH = 0.6f;
|
||||
|
@ -221,6 +222,7 @@ Keyboard::Keyboard() {
|
|||
auto pointerManager = DependencyManager::get<PointerManager>();
|
||||
auto windowScriptingInterface = DependencyManager::get<WindowScriptingInterface>();
|
||||
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::triggerContinueOverlay, this, &Keyboard::handleTriggerContinue, 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(myAvatar.get(), &MyAvatar::sensorToWorldScaleChanged, this, &Keyboard::scaleKeyboard, Qt::QueuedConnection);
|
||||
connect(windowScriptingInterface.data(), &WindowScriptingInterface::domainChanged, [&]() { setRaised(false); });
|
||||
connect(hmdScriptingInterface.data(), &HMDScriptingInterface::displayModeChanged, [&]() { setRaised(false); });
|
||||
}
|
||||
|
||||
void Keyboard::registerKeyboardHighlighting() {
|
||||
|
@ -483,7 +486,7 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent
|
|||
AudioInjectorOptions audioOptions;
|
||||
audioOptions.localOnly = true;
|
||||
audioOptions.position = keyWorldPosition;
|
||||
audioOptions.volume = 0.1f;
|
||||
audioOptions.volume = 0.05f;
|
||||
|
||||
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
|
||||
|
||||
|
@ -835,8 +838,8 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) {
|
|||
_textDisplay = textDisplay;
|
||||
|
||||
_ignoreItemsLock.withWriteLock([&] {
|
||||
_itemsToIgnore.push_back(_textDisplay.overlayID);
|
||||
_itemsToIgnore.push_back(_anchor.overlayID);
|
||||
_itemsToIgnore.append(_textDisplay.overlayID);
|
||||
_itemsToIgnore.append(_anchor.overlayID);
|
||||
});
|
||||
_layerIndex = 0;
|
||||
auto pointerManager = DependencyManager::get<PointerManager>();
|
||||
|
|
|
@ -535,7 +535,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
bool bestIsFront = false;
|
||||
bool bestIsTablet = false;
|
||||
auto tabletIDs = qApp->getTabletIDs();
|
||||
|
||||
const QVector<OverlayID> keyboardKeysToDiscard = DependencyManager::get<Keyboard>()->getKeysID();
|
||||
QMutexLocker locker(&_mutex);
|
||||
RayToOverlayIntersectionResult result;
|
||||
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
|
||||
|
@ -545,7 +545,8 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,39 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) {
|
|||
for (auto& joint : hfmModel.joints) {
|
||||
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) {
|
||||
buildSkeletonFromJoints(joints);
|
||||
AnimSkeleton::AnimSkeleton(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets) {
|
||||
buildSkeletonFromJoints(joints, jointOffsets);
|
||||
}
|
||||
|
||||
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;
|
||||
_jointsSize = (int)joints.size();
|
||||
// build a cache of bind poses
|
||||
|
@ -189,7 +218,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints)
|
|||
// build relative and absolute default poses
|
||||
glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform;
|
||||
AnimPose relDefaultPose(relDefaultMat);
|
||||
_relativeDefaultPoses.push_back(relDefaultPose);
|
||||
|
||||
int parentIndex = getParentIndex(i);
|
||||
if (parentIndex >= 0) {
|
||||
_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++) {
|
||||
_jointIndicesByName[_joints[i].name] = i;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ public:
|
|||
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
|
||||
|
||||
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;
|
||||
const QString& getJointName(int jointIndex) const;
|
||||
int getNumJoints() const;
|
||||
|
@ -62,9 +63,10 @@ public:
|
|||
void dump(const AnimPoseVec& poses) 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:
|
||||
void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints);
|
||||
void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets);
|
||||
|
||||
std::vector<HFMJoint> _joints;
|
||||
int _jointsSize { 0 };
|
||||
|
@ -76,6 +78,7 @@ protected:
|
|||
std::vector<int> _nonMirroredIndices;
|
||||
std::vector<int> _mirrorMap;
|
||||
QHash<QString, int> _jointIndicesByName;
|
||||
std::vector<std::vector<HFMCluster>> _clusterBindMatrixOriginalValues;
|
||||
|
||||
// no copies
|
||||
AnimSkeleton(const AnimSkeleton&) = delete;
|
||||
|
|
|
@ -360,8 +360,10 @@ void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset
|
|||
void Rig::reset(const HFMModel& hfmModel) {
|
||||
_geometryOffset = AnimPose(hfmModel.offset);
|
||||
_invGeometryOffset = _geometryOffset.inverse();
|
||||
|
||||
_animSkeleton = std::make_shared<AnimSkeleton>(hfmModel);
|
||||
|
||||
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
|
|
|
@ -417,6 +417,30 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
|||
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) {
|
||||
const FBXNode& node = _rootNode;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -311,6 +311,8 @@ public:
|
|||
QString getModelNameOfMesh(int meshIndex) const;
|
||||
|
||||
QList<QString> blendshapeChannelNames;
|
||||
|
||||
QMap<int, glm::quat> jointRotationOffsets;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -243,6 +243,13 @@ void GeometryReader::run() {
|
|||
QMetaObject::invokeMethod(resource.data(), "finishedLoading",
|
||||
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 gotVersion = false;
|
||||
|
||||
while (!(gotDataVersion && gotEntities && gotId && gotVersion)) {
|
||||
if (nextToken() != '"') {
|
||||
int token = nextToken();
|
||||
|
||||
while (true) {
|
||||
if (token == '}') {
|
||||
break;
|
||||
}
|
||||
else if (token != '"') {
|
||||
_errorString = "Incorrect key string";
|
||||
return false;
|
||||
}
|
||||
|
@ -144,15 +149,13 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (gotDataVersion && gotEntities && gotId && gotVersion) {
|
||||
break;
|
||||
} else if (nextToken() != ',') {
|
||||
_errorString = "Id/value incorrectly terminated";
|
||||
return false;
|
||||
token = nextToken();
|
||||
if (token == ',') {
|
||||
token = nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
if (nextToken() != '}' || nextToken() != -1) {
|
||||
if (nextToken() != -1) {
|
||||
_errorString = "Ill-formed end of object";
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -114,18 +114,22 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
for (int i = 0; i < (int)_meshStates.size(); i++) {
|
||||
Model::MeshState& state = _meshStates[i];
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
int meshIndex = i;
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
int clusterIndex = j;
|
||||
|
||||
if (_useDualQuaternionSkinning) {
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
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].setCauterizationParameters(0.0f, jointPose.trans());
|
||||
} else {
|
||||
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++) {
|
||||
Model::MeshState& state = _cauterizeMeshStates[i];
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
int meshIndex = i;
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
int clusterIndex = j;
|
||||
|
||||
if (_useDualQuaternionSkinning) {
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||
|
@ -157,7 +163,7 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
} else {
|
||||
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
||||
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].setCauterizationParameters(1.0f, cauterizePose.trans());
|
||||
}
|
||||
|
@ -166,7 +172,7 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
// not cauterized so just copy the value from the non-cauterized version.
|
||||
state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j];
|
||||
} 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();
|
||||
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
||||
MeshState& state = _meshStates[i];
|
||||
int meshIndex = i;
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
int clusterIndex = j;
|
||||
|
||||
if (_useDualQuaternionSkinning) {
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
} else {
|
||||
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++) {
|
||||
MeshState& state = _meshStates[i];
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
|
||||
int meshIndex = i;
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
|
||||
int clusterIndex = j;
|
||||
// TODO: cache these look-ups as an optimization
|
||||
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
if (_useDualQuaternionSkinning) {
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
glm::mat4 jointMatrix;
|
||||
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]);
|
||||
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -590,9 +590,6 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header {
|
|||
background-color: #373737;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.section-header span {
|
||||
font-size: 30px;
|
||||
|
@ -948,12 +945,12 @@ div.refresh input[type="button"] {
|
|||
}
|
||||
.draggable-number.left-arrow {
|
||||
top: -5px;
|
||||
right: 106px;
|
||||
left: 0px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.draggable-number.right-arrow {
|
||||
top: -5px;
|
||||
left: 106px;
|
||||
right: 0px;
|
||||
}
|
||||
.draggable-number input[type=number] {
|
||||
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;
|
||||
margin: 16px;
|
||||
}
|
||||
|
@ -1552,14 +1549,15 @@ input.rename-entity {
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 8px;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.container > label {
|
||||
margin-top: 6px;
|
||||
width: 200px;
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
max-width: 160px;
|
||||
}
|
||||
|
||||
.container > div.checkbox {
|
||||
|
@ -1600,6 +1598,8 @@ input.rename-entity {
|
|||
.xyz.fstuple, .pyr.fstuple {
|
||||
position: relative;
|
||||
left: -12px;
|
||||
min-width: 50px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.rgb.fstuple .tuple {
|
||||
|
@ -1650,3 +1650,15 @@ input.number-slider {
|
|||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.spacemode-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#placeholder-property-type {
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -19,47 +19,52 @@
|
|||
<script type="text/javascript" src="js/gridControls.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();'>
|
||||
<div id="grid-section">
|
||||
<div class="section-header">
|
||||
<label>Editing Grid</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type='checkbox' id="horiz-grid-visible">
|
||||
<div id="grid-section" class="section">
|
||||
<div class="property container">
|
||||
<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">
|
||||
<input type="checkbox" id="snap-to-grid">
|
||||
<div class="property container">
|
||||
<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 class="property container">
|
||||
<label for="major-spacing">Major grid size <span class="unit">m</span></label>
|
||||
<div class="number">
|
||||
<label for="major-spacing">Major grid size <span class="unit">m</span></label>
|
||||
<input type="number" id="major-spacing" min="1" step="1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="property container">
|
||||
<label for="minor-spacing">Minor grid size <span class="unit">m</span></label>
|
||||
<div class="number">
|
||||
<label for="minor-spacing">Minor grid size <span class="unit">m</span></label>
|
||||
<input type="number" id="minor-spacing" min="0.2" step="0.2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="property number">
|
||||
<div class="property container">
|
||||
<label for="horiz-y">Position (Y axis) <span class="unit">m</span></label>
|
||||
<input type="number" id="horiz-y" step="0.1" />
|
||||
</div>
|
||||
<div class="property rgb">
|
||||
<div id="grid-color" class="color-picker"></div>
|
||||
<label>Grid line color</label>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="grid-color-red"><label for="grid-color-red">Red:</label></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 style="width: 100%">
|
||||
<input type="number" id="horiz-y" step="0.1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<span>
|
||||
<div class="property container">
|
||||
<label>Grid line color</label>
|
||||
<div style="width: 100%">
|
||||
<div id="grid-color" class="color-picker"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property container">
|
||||
<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-avatar" value="Align To Avatar">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -32,7 +32,7 @@ DraggableNumber.prototype = {
|
|||
mouseUp: function(event) {
|
||||
if (event.target === this.elText && this.initialMouseEvent) {
|
||||
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.elText.style.visibility = "hidden";
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ const ICON_FOR_TYPE = {
|
|||
|
||||
const DEGREES_TO_RADIANS = Math.PI / 180.0;
|
||||
|
||||
const NO_SELECTION = "w";
|
||||
const NO_SELECTION = ",";
|
||||
|
||||
const PROPERTY_SPACE_MODE = {
|
||||
ALL: 0,
|
||||
|
@ -1158,18 +1158,21 @@ const GROUPS = [
|
|||
label: "Link",
|
||||
type: "string",
|
||||
propertyID: "href",
|
||||
placeholder: "URL",
|
||||
},
|
||||
{
|
||||
label: "Script",
|
||||
type: "string",
|
||||
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
|
||||
propertyID: "script",
|
||||
placeholder: "URL",
|
||||
},
|
||||
{
|
||||
label: "Server Script",
|
||||
type: "string",
|
||||
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ],
|
||||
propertyID: "serverScripts",
|
||||
placeholder: "URL",
|
||||
},
|
||||
{
|
||||
label: "Server Script Status",
|
||||
|
@ -1244,8 +1247,9 @@ const GROUPS = [
|
|||
showPropertyRule: { "collisionless": "false" },
|
||||
},
|
||||
{
|
||||
label: "Collision sound URL",
|
||||
label: "Collision Sound",
|
||||
type: "string",
|
||||
placeholder: "URL",
|
||||
propertyID: "collisionSoundURL",
|
||||
showPropertyRule: { "collisionless": "false" },
|
||||
},
|
||||
|
@ -1500,7 +1504,7 @@ function disableProperties() {
|
|||
|
||||
function showPropertyElement(propertyID, show) {
|
||||
let elProperty = properties[propertyID].elContainer;
|
||||
elProperty.style.display = show ? "flex" : "none";
|
||||
elProperty.style.display = show ? "" : "none";
|
||||
}
|
||||
|
||||
function resetProperties() {
|
||||
|
@ -1622,10 +1626,11 @@ function updateVisibleSpaceModeProperties() {
|
|||
if (properties.hasOwnProperty(propertyID)) {
|
||||
let property = properties[propertyID];
|
||||
let propertySpaceMode = property.spaceMode;
|
||||
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL) {
|
||||
showPropertyElement(propertyID, propertySpaceMode === currentSpaceMode);
|
||||
let elProperty = properties[propertyID].elContainer;
|
||||
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) {
|
||||
elProperty.classList.add('spacemode-hidden');
|
||||
} else {
|
||||
showPropertyElement(propertyID, true);
|
||||
elProperty.classList.remove('spacemode-hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2769,7 +2774,7 @@ function loaded() {
|
|||
elLegend.appendChild(createElementFromHTML(`<div class="label">${group.label}</div>`));
|
||||
|
||||
let elSpan = document.createElement('span');
|
||||
elSpan.className = ".collapse-icon";
|
||||
elSpan.className = "collapse-icon";
|
||||
elSpan.innerText = "M";
|
||||
elLegend.appendChild(elSpan);
|
||||
elGroup.appendChild(elLegend);
|
||||
|
@ -3309,14 +3314,14 @@ function loaded() {
|
|||
getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false));
|
||||
|
||||
// Collapsible sections
|
||||
let elCollapsible = document.getElementsByClassName("section-header");
|
||||
let elCollapsible = document.getElementsByClassName("collapse-icon");
|
||||
|
||||
let toggleCollapsedEvent = function(event) {
|
||||
let element = this.parentNode;
|
||||
let element = this.parentNode.parentNode;
|
||||
let isCollapsed = element.dataset.collapsed !== "true";
|
||||
element.dataset.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) {
|
||||
|
|
|
@ -83,44 +83,26 @@ function loaded() {
|
|||
|
||||
var gridColor = { red: 255, green: 255, blue: 255 };
|
||||
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 + ")";
|
||||
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) {
|
||||
elColorRed.value = red;
|
||||
elColorGreen.value = green;
|
||||
elColorBlue.value = blue;
|
||||
gridColor = { red: red, green: green, blue: blue };
|
||||
emitUpdate();
|
||||
};
|
||||
|
||||
elColorRed.addEventListener('change', colorChangeFunction);
|
||||
elColorGreen.addEventListener('change', colorChangeFunction);
|
||||
elColorBlue.addEventListener('change', colorChangeFunction);
|
||||
$('#grid-color').colpick({
|
||||
colorScheme: 'dark',
|
||||
layout: 'hex',
|
||||
layout: 'rgbhex',
|
||||
color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue },
|
||||
submit: false,
|
||||
onShow: function (colpick) {
|
||||
$('#grid-color').attr('active', 'true');
|
||||
},
|
||||
onHide: function (colpick) {
|
||||
$('#grid-color').attr('active', 'false');
|
||||
},
|
||||
onSubmit: function (hsb, hex, rgb, el) {
|
||||
onChange: function (hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#' + hex);
|
||||
$(el).colpickHide();
|
||||
colorPickFunction(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue