Merge branch 'master' of https://github.com/highfidelity/hifi into 19942-MacInstall

This commit is contained in:
NissimHadar 2018-11-20 08:43:40 -08:00
commit cbfe9f5440
30 changed files with 440 additions and 207 deletions

View file

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

View file

@ -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();
// use helper to add any changed traits to our packet list if (!overBudget) {
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); // use helper to add any changed traits to our packet list
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
}
remainingAvatars--; remainingAvatars--;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -141,6 +141,7 @@ TabletModalWindow {
Component.onDestruction: { Component.onDestruction: {
loginKeyboard.raised = false; loginKeyboard.raised = false;
KeyboardScriptingInterface.raised = false;
} }
Component.onCompleted: { Component.onCompleted: {

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
glm::mat4 jointMatrix;
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
} else {
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
}
if (_useDualQuaternionSkinning) { 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::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]);
} }
} }
} }

View file

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

View file

@ -19,47 +19,52 @@
<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">&nbsp;</label>
</div>
</div> </div>
<div class="property checkbox"> <div class="property container">
<input type="checkbox" id="snap-to-grid">
<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">&nbsp;</label>
</div>
</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"> <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" /> <input type="number" id="major-spacing" min="1" step="1" />
</div> </div>
</div>
<div class="property container">
<label for="minor-spacing">Minor grid size <span class="unit">m</span></label>
<div class="number"> <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" /> <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>
<input type="number" id="horiz-y" step="0.1" /> <div style="width: 100%">
</div> <input type="number" id="horiz-y" step="0.1" />
<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> </div>
</div> </div>
<div class="property"> <div class="property container">
<span> <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-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>
</html> </html>

View file

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

View file

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

View file

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