Merge branch 'master' of https://github.com/highfidelity/hifi into one

This commit is contained in:
Sam Gateau 2018-11-20 19:29:01 -08:00
commit dd828c81e0
33 changed files with 589 additions and 290 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

@ -234,7 +234,7 @@ private:
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
#else #else
Setting::Handle<bool> _enableInterstitialMode { "enableInterstitialMode", true }; Setting::Handle<bool> _enableInterstitialMode { "enableInterstitialMode", false };
#endif #endif
QSet<QString> _domainConnectionRefusals; QSet<QString> _domainConnectionRefusals;

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

@ -29,6 +29,7 @@
function MouseHMD() { function MouseHMD() {
var _this = this; var _this = this;
this.hmdWasActive = HMD.active;
this.mouseMoved = false; this.mouseMoved = false;
this.mouseActivity = new TimeLock(5000); this.mouseActivity = new TimeLock(5000);
this.handControllerActivity = new TimeLock(4000); this.handControllerActivity = new TimeLock(4000);
@ -102,6 +103,8 @@
this.isReady = function(controllerData, deltaTime) { this.isReady = function(controllerData, deltaTime) {
var now = Date.now(); var now = Date.now();
var hmdChanged = this.hmdWasActive !== HMD.active;
this.hmdWasActive = HMD.active;
this.triggersPressed(controllerData, now); this.triggersPressed(controllerData, now);
if (HMD.active) { if (HMD.active) {
if (!this.mouseActivity.expired(now) && _this.handControllerActivity.expired()) { if (!this.mouseActivity.expired(now) && _this.handControllerActivity.expired()) {
@ -110,7 +113,7 @@
} else { } else {
Reticle.visible = false; Reticle.visible = false;
} }
} else if (!Reticle.visible) { } else if (hmdChanged && !Reticle.visible) {
Reticle.visible = true; Reticle.visible = true;
} }

View file

@ -2223,6 +2223,7 @@ var PropertiesTool = function (opts) {
// are selected or if no entity is selected this will be `null`. // are selected or if no entity is selected this will be `null`.
var currentSelectedEntityID = null; var currentSelectedEntityID = null;
var statusMonitor = null; var statusMonitor = null;
var blockPropertyUpdates = false;
that.setVisible = function (newVisible) { that.setVisible = function (newVisible) {
visible = newVisible; visible = newVisible;
@ -2260,6 +2261,10 @@ var PropertiesTool = function (opts) {
}; };
function updateSelections(selectionUpdated) { function updateSelections(selectionUpdated) {
if (blockPropertyUpdates) {
return;
}
var data = { var data = {
type: 'update', type: 'update',
spaceMode: selectionDisplay.getSpaceMode() spaceMode: selectionDisplay.getSpaceMode()
@ -2356,7 +2361,9 @@ var PropertiesTool = function (opts) {
} }
} }
pushCommandForSelections(); pushCommandForSelections();
blockPropertyUpdates = data.blockUpdateCallback === true;
selectionManager._update(false, this); selectionManager._update(false, this);
blockPropertyUpdates = false;
} else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') {
//the event bridge and json parsing handle our avatar id string differently. //the event bridge and json parsing handle our avatar id string differently.
var actualID = data.id.split('"')[1]; var actualID = data.id.split('"')[1];
@ -2466,6 +2473,8 @@ var PropertiesTool = function (opts) {
tooltips: Script.require('./assets/data/createAppTooltips.json'), tooltips: Script.require('./assets/data/createAppTooltips.json'),
hmdActive: HMD.active, hmdActive: HMD.active,
}); });
} else if (data.type === "updateProperties") {
updateSelections(true);
} }
}; };

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

@ -8,11 +8,14 @@
const DELTA_X_FOCUS_THRESHOLD = 1; const DELTA_X_FOCUS_THRESHOLD = 1;
function DraggableNumber(min, max, step, decimals) { function DraggableNumber(min, max, step, decimals, dragStart, dragEnd) {
this.min = min; this.min = min;
this.max = max; this.max = max;
this.step = step !== undefined ? step : 1; this.step = step !== undefined ? step : 1;
this.decimals = decimals; this.decimals = decimals;
this.dragStartFunction = dragStart;
this.dragEndFunction = dragEnd;
this.dragging = false;
this.initialMouseEvent = null; this.initialMouseEvent = null;
this.lastMouseEvent = null; this.lastMouseEvent = null;
this.valueChangeFunction = null; this.valueChangeFunction = null;
@ -32,7 +35,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";
} }
@ -41,22 +44,32 @@ DraggableNumber.prototype = {
}, },
documentMouseMove: function(event) { documentMouseMove: function(event) {
if (this.lastMouseEvent) { if (this.initialMouseEvent) {
let initialValue = this.elInput.value; let dxFromInitial = event.clientX - this.initialMouseEvent.clientX;
let dx = event.clientX - this.lastMouseEvent.clientX; if (Math.abs(dxFromInitial) > DELTA_X_FOCUS_THRESHOLD && this.lastMouseEvent) {
let changeValue = dx !== 0; let initialValue = this.elInput.value;
if (changeValue) { let dx = event.clientX - this.lastMouseEvent.clientX;
while (dx !== 0) { let changeValue = dx !== 0;
if (dx > 0) { if (changeValue) {
this.stepUp(); while (dx !== 0) {
--dx; if (dx > 0) {
} else { this.elInput.stepUp();
this.stepDown(); --dx;
++dx; } else {
this.elInput.stepDown();
++dx;
}
}
this.inputChange();
if (this.valueChangeFunction) {
this.valueChangeFunction();
} }
} }
if (this.valueChangeFunction) { if (!this.dragging) {
this.valueChangeFunction(); if (this.dragStartFunction) {
this.dragStartFunction();
}
this.dragging = true;
} }
} }
this.lastMouseEvent = event; this.lastMouseEvent = event;
@ -64,6 +77,12 @@ DraggableNumber.prototype = {
}, },
documentMouseUp: function(event) { documentMouseUp: function(event) {
if (this.dragging) {
if (this.dragEndFunction) {
this.dragEndFunction();
}
this.dragging = false;
}
this.lastMouseEvent = null; this.lastMouseEvent = null;
document.removeEventListener("mousemove", this.onDocumentMouseMove); document.removeEventListener("mousemove", this.onDocumentMouseMove);
document.removeEventListener("mouseup", this.onDocumentMouseUp); document.removeEventListener("mouseup", this.onDocumentMouseUp);
@ -72,11 +91,17 @@ DraggableNumber.prototype = {
stepUp: function() { stepUp: function() {
this.elInput.stepUp(); this.elInput.stepUp();
this.inputChange(); this.inputChange();
if (this.valueChangeFunction) {
this.valueChangeFunction();
}
}, },
stepDown: function() { stepDown: function() {
this.elInput.stepDown(); this.elInput.stepDown();
this.inputChange(); this.inputChange();
if (this.valueChangeFunction) {
this.valueChangeFunction();
}
}, },
setValue: function(newValue) { setValue: function(newValue) {

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');
} }
} }
} }
@ -1636,7 +1641,7 @@ function updateVisibleSpaceModeProperties() {
* PROPERTY UPDATE FUNCTIONS * PROPERTY UPDATE FUNCTIONS
*/ */
function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { function updateProperty(originalPropertyName, propertyValue, isParticleProperty, blockUpdateCallback) {
let propertyUpdate = {}; let propertyUpdate = {};
// if this is a compound property name (i.e. animation.running) then split it by . up to 3 times // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times
let splitPropertyName = originalPropertyName.split('.'); let splitPropertyName = originalPropertyName.split('.');
@ -1662,7 +1667,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty)
}); });
particleSyncDebounce(); particleSyncDebounce();
} else { } else {
updateProperties(propertyUpdate); updateProperties(propertyUpdate, blockUpdateCallback);
} }
} }
@ -1671,66 +1676,89 @@ var particleSyncDebounce = _.debounce(function () {
particlePropertyUpdates = {}; particlePropertyUpdates = {};
}, DEBOUNCE_TIMEOUT); }, DEBOUNCE_TIMEOUT);
function updateProperties(propertiesToUpdate) { function updateProperties(propertiesToUpdate, blockUpdateCallback) {
if (blockUpdateCallback === undefined) {
blockUpdateCallback = false;
}
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
id: lastEntityID, id: lastEntityID,
type: "update", type: "update",
properties: propertiesToUpdate properties: propertiesToUpdate,
blockUpdateCallback: blockUpdateCallback
})); }));
} }
function createEmitTextPropertyUpdateFunction(propertyName, isParticleProperty) { function createEmitTextPropertyUpdateFunction(property) {
return function() { return function() {
updateProperty(propertyName, this.value, isParticleProperty); updateProperty(property.name, this.value, property.isParticleProperty);
}; };
} }
function createEmitCheckedPropertyUpdateFunction(propertyName, inverse, isParticleProperty) { function createEmitCheckedPropertyUpdateFunction(property) {
return function() { return function() {
updateProperty(propertyName, inverse ? !this.checked : this.checked, isParticleProperty); updateProperty(property.name, property.data.inverse ? !this.checked : this.checked, property.isParticleProperty);
}; };
} }
function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, isParticleProperty) { function createDragStartFunction(property) {
return function() { return function() {
property.dragging = true;
};
}
function createDragEndFunction(property) {
return function() {
property.dragging = false;
EventBridge.emitWebEvent(JSON.stringify({
type: "updateProperties"
}));
};
}
function createEmitNumberPropertyUpdateFunction(property) {
return function() {
let multiplier = property.data.multiplier;
if (multiplier === undefined) { if (multiplier === undefined) {
multiplier = 1; multiplier = 1;
} }
let value = parseFloat(this.value) * multiplier; let value = parseFloat(this.value) * multiplier;
updateProperty(propertyName, value, isParticleProperty); updateProperty(property.name, value, property.isParticleProperty, property.dragging);
}; };
} }
function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier, isParticleProperty) { function createEmitVec2PropertyUpdateFunction(property) {
return function () { return function () {
let multiplier = property.data.multiplier;
if (multiplier === undefined) { if (multiplier === undefined) {
multiplier = 1; multiplier = 1;
} }
let newValue = { let newValue = {
x: elX.value * multiplier, x: property.elNumberX.elInput.value * multiplier,
y: elY.value * multiplier y: property.elNumberY.elInput.value * multiplier
}; };
updateProperty(propertyName, newValue, isParticleProperty); updateProperty(property.name, newValue, property.isParticleProperty, property.dragging);
}; };
} }
function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier, isParticleProperty) { function createEmitVec3PropertyUpdateFunction(property) {
return function() { return function() {
let multiplier = property.data.multiplier;
if (multiplier === undefined) { if (multiplier === undefined) {
multiplier = 1; multiplier = 1;
} }
let newValue = { let newValue = {
x: elX.value * multiplier, x: property.elNumberX.elInput.value * multiplier,
y: elY.value * multiplier, y: property.elNumberY.elInput.value * multiplier,
z: elZ.value * multiplier z: property.elNumberZ.elInput.value * multiplier
}; };
updateProperty(propertyName, newValue, isParticleProperty); updateProperty(property.name, newValue, property.isParticleProperty, property.dragging);
}; };
} }
function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue, isParticleProperty) { function createEmitColorPropertyUpdateFunction(property) {
return function() { return function() {
emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value, isParticleProperty); emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value,
property.elNumberB.elInput.value, property.isParticleProperty);
}; };
} }
@ -1755,10 +1783,10 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen
updateProperty(propertyName, propertyValue, isParticleProperty); updateProperty(propertyName, propertyValue, isParticleProperty);
} }
function createImageURLUpdateFunction(propertyName, isParticleProperty) { function createImageURLUpdateFunction(property) {
return function () { return function () {
let newTextures = JSON.stringify({ "tex.picture": this.value }); let newTextures = JSON.stringify({ "tex.picture": this.value });
updateProperty(propertyName, newTextures, isParticleProperty); updateProperty(property.name, newTextures, property.isParticleProperty);
}; };
} }
@ -1768,7 +1796,6 @@ function createImageURLUpdateFunction(propertyName, isParticleProperty) {
*/ */
function createStringProperty(property, elProperty) { function createStringProperty(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID; let elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
@ -1782,7 +1809,7 @@ function createStringProperty(property, elProperty) {
`) `)
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
elProperty.appendChild(elInput); elProperty.appendChild(elInput);
@ -1821,30 +1848,29 @@ function createBoolProperty(property, elProperty) {
elInput, propertyName, property.isParticleProperty); elInput, propertyName, property.isParticleProperty);
}); });
} else { } else {
elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(property));
property.isParticleProperty));
} }
return elInput; return elInput;
} }
function createNumberProperty(property, elProperty) { function createNumberProperty(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID; let elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
elProperty.className = "draggable-number"; elProperty.className = "draggable-number";
let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, let dragStartFunction = createDragStartFunction(property);
propertyData.step, propertyData.decimals); let dragEndFunction = createDragEndFunction(property);
let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step,
propertyData.decimals, dragStartFunction, dragEndFunction);
let defaultValue = propertyData.defaultValue; let defaultValue = propertyData.defaultValue;
if (defaultValue !== undefined) { if (defaultValue !== undefined) {
elDraggableNumber.elInput.value = defaultValue; elDraggableNumber.elInput.value = defaultValue;
} }
let valueChangeFunction = createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, let valueChangeFunction = createEmitNumberPropertyUpdateFunction(property);
property.isParticleProperty);
elDraggableNumber.setValueChangeFunction(valueChangeFunction); elDraggableNumber.setValueChangeFunction(valueChangeFunction);
elDraggableNumber.elInput.setAttribute("id", elementID); elDraggableNumber.elInput.setAttribute("id", elementID);
@ -1858,22 +1884,18 @@ function createNumberProperty(property, elProperty) {
} }
function createVec3Property(property, elProperty) { function createVec3Property(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
elProperty.className = propertyData.vec3Type + " fstuple"; elProperty.className = propertyData.vec3Type + " fstuple";
let elNumberX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER], let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]);
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]);
let elNumberY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER], let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]);
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); elProperty.appendChild(elNumberX.elDiv);
let elNumberZ = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER], elProperty.appendChild(elNumberY.elDiv);
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); elProperty.appendChild(elNumberZ.elDiv);
let valueChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elNumberX.elInput, elNumberY.elInput, let valueChangeFunction = createEmitVec3PropertyUpdateFunction(property);
elNumberZ.elInput, propertyData.multiplier,
property.isParticleProperty);
elNumberX.setValueChangeFunction(valueChangeFunction); elNumberX.setValueChangeFunction(valueChangeFunction);
elNumberY.setValueChangeFunction(valueChangeFunction); elNumberY.setValueChangeFunction(valueChangeFunction);
elNumberZ.setValueChangeFunction(valueChangeFunction); elNumberZ.setValueChangeFunction(valueChangeFunction);
@ -1886,8 +1908,6 @@ function createVec3Property(property, elProperty) {
} }
function createVec2Property(property, elProperty) { function createVec2Property(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
elProperty.className = propertyData.vec2Type + " fstuple"; elProperty.className = propertyData.vec2Type + " fstuple";
@ -1897,13 +1917,12 @@ function createVec2Property(property, elProperty) {
elProperty.appendChild(elTuple); elProperty.appendChild(elTuple);
let elNumberX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER], let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]);
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]);
let elNumberY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER], elProperty.appendChild(elNumberX.elDiv);
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); elProperty.appendChild(elNumberY.elDiv);
let valueChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elNumberX.elInput, elNumberY.elInput, let valueChangeFunction = createEmitVec2PropertyUpdateFunction(property);
propertyData.multiplier, property.isParticleProperty);
elNumberX.setValueChangeFunction(valueChangeFunction); elNumberX.setValueChangeFunction(valueChangeFunction);
elNumberY.setValueChangeFunction(valueChangeFunction); elNumberY.setValueChangeFunction(valueChangeFunction);
@ -1916,6 +1935,7 @@ function createVec2Property(property, elProperty) {
function createColorProperty(property, elProperty) { function createColorProperty(property, elProperty) {
let propertyName = property.name; let propertyName = property.name;
let elementID = property.elementID; let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "rgb fstuple"; elProperty.className = "rgb fstuple";
@ -1929,12 +1949,24 @@ function createColorProperty(property, elProperty) {
elProperty.appendChild(elColorPicker); elProperty.appendChild(elColorPicker);
elProperty.appendChild(elTuple); elProperty.appendChild(elTuple);
let elNumberR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP); if (propertyData.min === undefined) {
let elNumberG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); propertyData.min = COLOR_MIN;
let elNumberB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); }
if (propertyData.max === undefined) {
propertyData.max = COLOR_MAX;
}
if (propertyData.step === undefined) {
propertyData.step = COLOR_STEP;
}
let valueChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elNumberR.elInput, elNumberG.elInput, let elNumberR = createTupleNumberInput(property, "red");
elNumberB.elInput, property.isParticleProperty); let elNumberG = createTupleNumberInput(property, "green");
let elNumberB = createTupleNumberInput(property, "blue");
elTuple.appendChild(elNumberR.elDiv);
elTuple.appendChild(elNumberG.elDiv);
elTuple.appendChild(elNumberB.elDiv);
let valueChangeFunction = createEmitColorPropertyUpdateFunction(property);
elNumberR.setValueChangeFunction(valueChangeFunction); elNumberR.setValueChangeFunction(valueChangeFunction);
elNumberG.setValueChangeFunction(valueChangeFunction); elNumberG.setValueChangeFunction(valueChangeFunction);
elNumberB.setValueChangeFunction(valueChangeFunction); elNumberB.setValueChangeFunction(valueChangeFunction);
@ -1973,7 +2005,6 @@ function createColorProperty(property, elProperty) {
} }
function createDropdownProperty(property, propertyID, elProperty) { function createDropdownProperty(property, propertyID, elProperty) {
let propertyName = property.name;
let elementID = property.elementID; let elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
@ -1990,7 +2021,7 @@ function createDropdownProperty(property, propertyID, elProperty) {
elInput.add(option); elInput.add(option);
} }
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
elProperty.appendChild(elInput); elProperty.appendChild(elInput);
@ -1998,7 +2029,6 @@ function createDropdownProperty(property, propertyID, elProperty) {
} }
function createTextareaProperty(property, elProperty) { function createTextareaProperty(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID; let elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
@ -2010,7 +2040,7 @@ function createTextareaProperty(property, elProperty) {
elInput.readOnly = true; elInput.readOnly = true;
} }
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
elProperty.appendChild(elInput); elProperty.appendChild(elInput);
@ -2102,7 +2132,9 @@ function createButtonsProperty(property, elProperty, elLabel) {
return elProperty; return elProperty;
} }
function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step, decimals) { function createTupleNumberInput(property, subLabel) {
let propertyElementID = property.elementID;
let propertyData = property.data;
let elementID = propertyElementID + "-" + subLabel.toLowerCase(); let elementID = propertyElementID + "-" + subLabel.toLowerCase();
let elLabel = document.createElement('label'); let elLabel = document.createElement('label');
@ -2111,11 +2143,13 @@ function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max,
elLabel.setAttribute("for", elementID); elLabel.setAttribute("for", elementID);
elLabel.style.visibility = "visible"; elLabel.style.visibility = "visible";
let elDraggableNumber = new DraggableNumber(min, max, step, decimals); let dragStartFunction = createDragStartFunction(property);
let dragEndFunction = createDragEndFunction(property);
let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step,
propertyData.decimals, dragStartFunction, dragEndFunction);
elDraggableNumber.elInput.setAttribute("id", elementID); elDraggableNumber.elInput.setAttribute("id", elementID);
elDraggableNumber.elDiv.className += " fstuple"; elDraggableNumber.elDiv.className += " fstuple";
elDraggableNumber.elText.insertBefore(elLabel, elDraggableNumber.elLeftArrow); elDraggableNumber.elText.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
elTuple.appendChild(elDraggableNumber.elDiv);
return elDraggableNumber; return elDraggableNumber;
} }
@ -2388,7 +2422,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
userDataElement.value = propertyUpdate.userData; userDataElement.value = propertyUpdate.userData;
updateProperties(propertyUpdate); updateProperties(propertyUpdate, false);
} }
var editor = null; var editor = null;
@ -2769,7 +2803,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);
@ -3306,17 +3340,17 @@ function loaded() {
} }
}); });
getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false)); getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction(properties['textures']));
// 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) {
@ -3401,7 +3435,7 @@ function loaded() {
let propertyID = elDropdown.getAttribute("propertyID"); let propertyID = elDropdown.getAttribute("propertyID");
let property = properties[propertyID]; let property = properties[propertyID];
property.elInput = dt; property.elInput = dt;
dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name, property.isParticleProperty)); dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
} }
elDropdowns = document.getElementsByTagName("select"); elDropdowns = document.getElementsByTagName("select");

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