mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Merge branch 'master' into hfm_serializer
This commit is contained in:
commit
be511378cd
204 changed files with 5604 additions and 2813 deletions
|
@ -754,13 +754,13 @@ void Agent::processAgentAvatarAudio() {
|
|||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
if (_avatarSound) {
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
auto audioData = _avatarSound->getAudioData();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(audioData->rawData()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
int numAvailableBytes = (audioData->getNumBytes() - _numAvatarSoundSentBytes) > AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
? AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
: audioData->getNumBytes() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = (int16_t)numAvailableBytes / sizeof(int16_t);
|
||||
|
||||
|
||||
|
@ -773,7 +773,7 @@ void Agent::processAgentAvatarAudio() {
|
|||
}
|
||||
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
if (_numAvatarSoundSentBytes == (int)audioData->getNumBytes()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
// and our sent bytes back to zero
|
||||
_avatarSound.clear();
|
||||
|
|
|
@ -435,7 +435,11 @@ void AudioMixer::start() {
|
|||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
int numToRetain = nodeList->size() * (1 - _throttlingRatio);
|
||||
int numToRetain = -1;
|
||||
assert(_throttlingRatio >= 0.0f && _throttlingRatio <= 1.0f);
|
||||
if (_throttlingRatio > EPSILON) {
|
||||
numToRetain = nodeList->size() * (1.0f - _throttlingRatio);
|
||||
}
|
||||
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
||||
// mix across slave threads
|
||||
auto mixTimer = _mixTiming.timer();
|
||||
|
@ -488,11 +492,8 @@ void AudioMixer::throttle(chrono::microseconds duration, int frame) {
|
|||
|
||||
// target different mix and backoff ratios (they also have different backoff rates)
|
||||
// this is to prevent oscillation, and encourage throttling to find a steady state
|
||||
const float TARGET = 0.9f;
|
||||
// on a "regular" machine with 100 avatars, this is the largest value where
|
||||
// - overthrottling can be recovered
|
||||
// - oscillations will not occur after the recovery
|
||||
const float BACKOFF_TARGET = 0.44f;
|
||||
const float TARGET = _throttleStartTarget;
|
||||
const float BACKOFF_TARGET = _throttleBackoffTarget;
|
||||
|
||||
// the mixer is known to struggle at about 80 on a "regular" machine
|
||||
// so throttle 2/80 the streams to ensure smooth audio (throttling is linear)
|
||||
|
@ -551,6 +552,24 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
_slavePool.setNumThreads(numThreads);
|
||||
}
|
||||
}
|
||||
|
||||
const QString THROTTLE_START_KEY = "throttle_start";
|
||||
const QString THROTTLE_BACKOFF_KEY = "throttle_backoff";
|
||||
|
||||
float settingsThrottleStart = audioThreadingGroupObject[THROTTLE_START_KEY].toDouble(_throttleStartTarget);
|
||||
float settingsThrottleBackoff = audioThreadingGroupObject[THROTTLE_BACKOFF_KEY].toDouble(_throttleBackoffTarget);
|
||||
|
||||
if (settingsThrottleBackoff > settingsThrottleStart) {
|
||||
qCWarning(audio) << "Throttle backoff target cannot be higher than throttle start target. Using default values.";
|
||||
} else if (settingsThrottleBackoff < 0.0f || settingsThrottleStart > 1.0f) {
|
||||
qCWarning(audio) << "Throttle start and backoff targets must be greater than or equal to 0.0"
|
||||
<< "and lesser than or equal to 1.0. Using default values.";
|
||||
} else {
|
||||
_throttleStartTarget = settingsThrottleStart;
|
||||
_throttleBackoffTarget = settingsThrottleBackoff;
|
||||
}
|
||||
|
||||
qCDebug(audio) << "Throttle Start:" << _throttleStartTarget << "Throttle Backoff:" << _throttleBackoffTarget;
|
||||
}
|
||||
|
||||
if (settingsObject.contains(AUDIO_BUFFER_GROUP_KEY)) {
|
||||
|
|
|
@ -144,11 +144,13 @@ private:
|
|||
static std::map<QString, CodecPluginPointer> _availableCodecs;
|
||||
static QStringList _codecPreferenceOrder;
|
||||
|
||||
|
||||
static std::vector<ZoneDescription> _audioZones;
|
||||
static std::vector<ZoneSettings> _zoneSettings;
|
||||
static std::vector<ReverbSettings> _zoneReverbSettings;
|
||||
|
||||
float _throttleStartTarget = 0.9f;
|
||||
float _throttleBackoffTarget = 0.44f;
|
||||
|
||||
AudioMixerSlave::SharedData _workerSharedData;
|
||||
};
|
||||
|
||||
|
|
|
@ -416,7 +416,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||
bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame;
|
||||
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
||||
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
||||
if (overBudget) {
|
||||
if (PALIsOpen) {
|
||||
_stats.overBudgetAvatars++;
|
||||
|
@ -497,8 +498,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
_stats.avatarDataPackingElapsedTime +=
|
||||
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||
if (!overBudget) {
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||
}
|
||||
|
||||
remainingAvatars--;
|
||||
}
|
||||
|
||||
|
|
|
@ -1012,6 +1012,24 @@
|
|||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "throttle_start",
|
||||
"type": "double",
|
||||
"label": "Throttle Start Target",
|
||||
"help": "Target percentage of frame time to start throttling",
|
||||
"placeholder": "0.9",
|
||||
"default": 0.9,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "throttle_backoff",
|
||||
"type": "double",
|
||||
"label": "Throttle Backoff Target",
|
||||
"help": "Target percentage of frame time to backoff throttling",
|
||||
"placeholder": "0.44",
|
||||
"default": 0.44,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -10,10 +10,85 @@ $(document).ready(function(){
|
|||
function progressBarHTML(extraClass, label) {
|
||||
var html = "<div class='progress'>";
|
||||
html += "<div class='" + extraClass + " progress-bar progress-bar-success progress-bar-striped active' role='progressbar' aria-valuemin='0' aria-valuemax='100'>";
|
||||
html += label + "<span class='sr-only'></span></div></div>";
|
||||
html += "<span class='ongoing-msg'></span></div></div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function showUploadProgress(title) {
|
||||
swal({
|
||||
title: title,
|
||||
text: progressBarHTML('upload-content-progress', 'Upload'),
|
||||
html: true,
|
||||
showConfirmButton: false,
|
||||
allowEscapeKey: false
|
||||
});
|
||||
}
|
||||
|
||||
function uploadNextChunk(file, offset, id) {
|
||||
if (offset == undefined) {
|
||||
offset = 0;
|
||||
}
|
||||
if (id == undefined) {
|
||||
// Identify this upload session
|
||||
id = Math.round(Math.random() * 2147483647);
|
||||
}
|
||||
|
||||
var fileSize = file.size;
|
||||
var filename = file.name;
|
||||
|
||||
var CHUNK_SIZE = 1048576; // 1 MiB
|
||||
|
||||
var isFinal = Boolean(fileSize - offset <= CHUNK_SIZE);
|
||||
var nextChunkSize = Math.min(fileSize - offset, CHUNK_SIZE);
|
||||
var chunk = file.slice(offset, offset + nextChunkSize, file.type);
|
||||
var chunkFormData = new FormData();
|
||||
|
||||
var formItemName = 'restore-file-chunk';
|
||||
if (offset == 0) {
|
||||
formItemName = isFinal ? 'restore-file-chunk-only' : 'restore-file-chunk-initial';
|
||||
} else if (isFinal) {
|
||||
formItemName = 'restore-file-chunk-final';
|
||||
}
|
||||
|
||||
chunkFormData.append(formItemName, chunk, filename);
|
||||
var ajaxParams = {
|
||||
url: '/content/upload',
|
||||
type: 'POST',
|
||||
timeout: 30000, // 30 s
|
||||
headers: {"X-Session-Id": id},
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: chunkFormData
|
||||
};
|
||||
|
||||
var ajaxObject = $.ajax(ajaxParams);
|
||||
ajaxObject.fail(function (jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(
|
||||
"Error",
|
||||
"There was a problem restoring domain content.\n"
|
||||
+ "Please ensure that the content archive or entity file is valid and try again."
|
||||
);
|
||||
});
|
||||
|
||||
updateProgressBars($('.upload-content-progress'), (offset + nextChunkSize) * 100 / fileSize);
|
||||
|
||||
if (!isFinal) {
|
||||
ajaxObject.done(function (data, textStatus, jqXHR)
|
||||
{ uploadNextChunk(file, offset + CHUNK_SIZE, id); });
|
||||
} else {
|
||||
ajaxObject.done(function(data, textStatus, jqXHR) {
|
||||
isRestoring = true;
|
||||
|
||||
// immediately reload backup information since one should be restoring now
|
||||
reloadBackupInformation();
|
||||
|
||||
swal.close();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setupBackupUpload() {
|
||||
// construct the HTML needed for the settings backup panel
|
||||
var html = "<div class='form-group'><div id='" + UPLOAD_CONTENT_ALLOWED_DIV_ID + "'>";
|
||||
|
@ -50,34 +125,10 @@ $(document).ready(function(){
|
|||
"Restore content",
|
||||
function() {
|
||||
var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files');
|
||||
var file = files[0];
|
||||
|
||||
var fileFormData = new FormData();
|
||||
fileFormData.append('restore-file', files[0]);
|
||||
|
||||
showSpinnerAlert("Uploading content to restore");
|
||||
|
||||
$.ajax({
|
||||
url: '/content/upload',
|
||||
type: 'POST',
|
||||
timeout: 3600000, // Set timeout to 1h
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: fileFormData
|
||||
}).done(function(data, textStatus, jqXHR) {
|
||||
isRestoring = true;
|
||||
|
||||
// immediately reload backup information since one should be restoring now
|
||||
reloadBackupInformation();
|
||||
|
||||
swal.close();
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(
|
||||
"Error",
|
||||
"There was a problem restoring domain content.\n"
|
||||
+ "Please ensure that the content archive or entity file is valid and try again."
|
||||
);
|
||||
});
|
||||
showUploadProgress("Uploading " + file.name);
|
||||
uploadNextChunk(file);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -168,6 +219,11 @@ $(document).ready(function(){
|
|||
checkBackupStatus();
|
||||
});
|
||||
|
||||
function updateProgressBars($progressBar, value) {
|
||||
$progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%');
|
||||
$progressBar.find('.ongoing-msg').html(" " + Math.round(value) + "%");
|
||||
}
|
||||
|
||||
function reloadBackupInformation() {
|
||||
// make a GET request to get backup information to populate the table
|
||||
$.ajax({
|
||||
|
@ -204,11 +260,6 @@ $(document).ready(function(){
|
|||
+ "<li><a class='" + BACKUP_DELETE_LINK_CLASS + "' href='#' target='_blank'>Delete</a></li></ul></div></td>";
|
||||
}
|
||||
|
||||
function updateProgressBars($progressBar, value) {
|
||||
$progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%');
|
||||
$progressBar.find('.sr-only').html(value + "% Complete");
|
||||
}
|
||||
|
||||
// before we add any new rows and update existing ones
|
||||
// remove our flag for active rows
|
||||
$('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS);
|
||||
|
|
|
@ -348,6 +348,27 @@ void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise
|
|||
});
|
||||
}
|
||||
|
||||
void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "recoverFromUploadedFile", Q_ARG(MiniPromise::Promise, promise),
|
||||
Q_ARG(QString, uploadedFilename));
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Recovering from uploaded file -" << uploadedFilename;
|
||||
|
||||
QFile uploadedFile(uploadedFilename);
|
||||
QuaZip uploadedZip { &uploadedFile };
|
||||
|
||||
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
|
||||
|
||||
bool success = recoverFromBackupZip(backupName, uploadedZip);
|
||||
|
||||
promise->resolve({
|
||||
{ "success", success }
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<BackupItemInfo> DomainContentBackupManager::getAllBackups() {
|
||||
|
||||
QDir backupDir { _backupDirectory };
|
||||
|
|
|
@ -86,6 +86,7 @@ public slots:
|
|||
void createManualBackup(MiniPromise::Promise promise, const QString& name);
|
||||
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
|
||||
void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename);
|
||||
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||
|
||||
signals:
|
||||
|
|
|
@ -2258,46 +2258,18 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
// check the file extension to see what kind of file this is
|
||||
// to make sure we handle this filetype for a content restore
|
||||
auto dispositionValue = QString(firstFormData.first.value("Content-Disposition"));
|
||||
auto formDataFilenameRegex = QRegExp("filename=\"(.+)\"");
|
||||
auto matchIndex = formDataFilenameRegex.indexIn(dispositionValue);
|
||||
QRegExp formDataFieldsRegex(R":(name="(restore-file.*)".*filename="(.+)"):");
|
||||
auto matchIndex = formDataFieldsRegex.indexIn(dispositionValue);
|
||||
|
||||
QString formItemName = "";
|
||||
QString uploadedFilename = "";
|
||||
if (matchIndex != -1) {
|
||||
uploadedFilename = formDataFilenameRegex.cap(1);
|
||||
}
|
||||
|
||||
if (uploadedFilename.endsWith(".json", Qt::CaseInsensitive)
|
||||
|| uploadedFilename.endsWith(".json.gz", Qt::CaseInsensitive)) {
|
||||
// invoke our method to hand the new octree file off to the octree server
|
||||
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
||||
Qt::QueuedConnection, Q_ARG(QByteArray, firstFormData.second));
|
||||
|
||||
// respond with a 200 for success
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
} else if (uploadedFilename.endsWith(".zip", Qt::CaseInsensitive)) {
|
||||
auto deferred = makePromise("recoverFromUploadedBackup");
|
||||
|
||||
deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) {
|
||||
if (!connectionPtr) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootJSON;
|
||||
auto success = result["success"].toBool();
|
||||
rootJSON["success"] = success;
|
||||
QJsonDocument docJSON(rootJSON);
|
||||
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
|
||||
JSON_MIME_TYPE.toUtf8());
|
||||
});
|
||||
|
||||
_contentManager->recoverFromUploadedBackup(deferred, firstFormData.second);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// we don't have handling for this filetype, send back a 400 for failure
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
formItemName = formDataFieldsRegex.cap(1);
|
||||
uploadedFilename = formDataFieldsRegex.cap(2);
|
||||
}
|
||||
|
||||
// Received a chunk
|
||||
processPendingContent(connection, formItemName, uploadedFilename, firstFormData.second);
|
||||
} else {
|
||||
// respond with a 400 for failure
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
|
@ -2546,6 +2518,72 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
|||
}
|
||||
}
|
||||
|
||||
bool DomainServer::processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk) {
|
||||
static const QString UPLOAD_SESSION_KEY { "X-Session-Id" };
|
||||
QByteArray sessionIdBytes = connection->requestHeader(UPLOAD_SESSION_KEY);
|
||||
int sessionId = sessionIdBytes.toInt();
|
||||
|
||||
bool newUpload = itemName == "restore-file" || itemName == "restore-file-chunk-initial" || itemName == "restore-file-chunk-only";
|
||||
|
||||
if (filename.endsWith(".zip", Qt::CaseInsensitive)) {
|
||||
static const QString TEMPORARY_CONTENT_FILEPATH { QDir::tempPath() + "/hifiUploadContent_XXXXXX.zip" };
|
||||
|
||||
if (_pendingContentFiles.find(sessionId) == _pendingContentFiles.end()) {
|
||||
if (!newUpload) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<QTemporaryFile> newTemp(new QTemporaryFile(TEMPORARY_CONTENT_FILEPATH));
|
||||
_pendingContentFiles[sessionId] = std::move(newTemp);
|
||||
} else if (newUpload) {
|
||||
qCDebug(domain_server) << "New upload received using existing session ID";
|
||||
_pendingContentFiles[sessionId]->resize(0);
|
||||
}
|
||||
|
||||
QTemporaryFile& _pendingFileContent = *_pendingContentFiles[sessionId];
|
||||
if (!_pendingFileContent.open()) {
|
||||
_pendingContentFiles.erase(sessionId);
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
return false;
|
||||
}
|
||||
_pendingFileContent.seek(_pendingFileContent.size());
|
||||
_pendingFileContent.write(dataChunk);
|
||||
_pendingFileContent.close();
|
||||
|
||||
// Respond immediately - will timeout if we wait for restore.
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||
auto deferred = makePromise("recoverFromUploadedBackup");
|
||||
|
||||
deferred->then([this, sessionId](QString error, QVariantMap result) {
|
||||
_pendingContentFiles.erase(sessionId);
|
||||
});
|
||||
|
||||
_contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName());
|
||||
}
|
||||
} else if (filename.endsWith(".json", Qt::CaseInsensitive)
|
||||
|| filename.endsWith(".json.gz", Qt::CaseInsensitive)) {
|
||||
if (_pendingUploadedContents.find(sessionId) == _pendingUploadedContents.end() && !newUpload) {
|
||||
qCDebug(domain_server) << "Json upload with invalid session ID received";
|
||||
return false;
|
||||
}
|
||||
QByteArray& _pendingUploadedContent = _pendingUploadedContents[sessionId];
|
||||
_pendingUploadedContent += dataChunk;
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
|
||||
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||
// invoke our method to hand the new octree file off to the octree server
|
||||
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
||||
Qt::QueuedConnection, Q_ARG(QByteArray, _pendingUploadedContent));
|
||||
_pendingUploadedContents.erase(sessionId);
|
||||
}
|
||||
} else {
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) {
|
||||
// grab the UUID state property from the reply
|
||||
QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid();
|
||||
|
@ -3411,20 +3449,11 @@ void DomainServer::maybeHandleReplacementEntityFile() {
|
|||
}
|
||||
|
||||
void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
|
||||
//Assume we have compressed data
|
||||
auto compressedOctree = octreeFile;
|
||||
QByteArray jsonOctree;
|
||||
|
||||
bool wasCompressed = gunzip(compressedOctree, jsonOctree);
|
||||
if (!wasCompressed) {
|
||||
// the source was not compressed, assume we were sent regular JSON data
|
||||
jsonOctree = compressedOctree;
|
||||
}
|
||||
|
||||
OctreeUtils::RawEntityData data;
|
||||
if (data.readOctreeDataInfoFromData(jsonOctree)) {
|
||||
if (data.readOctreeDataInfoFromData(octreeFile)) {
|
||||
data.resetIdAndVersion();
|
||||
|
||||
QByteArray compressedOctree;
|
||||
gzip(data.toByteArray(), compressedOctree);
|
||||
|
||||
// write the compressed octree data to a special file
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QHostAddress>
|
||||
#include <QAbstractNativeEventFilter>
|
||||
|
||||
#include <Assignment.h>
|
||||
|
@ -209,6 +210,8 @@ private:
|
|||
|
||||
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
|
||||
|
||||
bool processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk);
|
||||
|
||||
bool forwardMetaverseAPIRequest(HTTPConnection* connection,
|
||||
const QString& metaversePath,
|
||||
const QString& requestSubobject,
|
||||
|
@ -281,6 +284,9 @@ private:
|
|||
|
||||
QHash<QUuid, QPointer<HTTPSConnection>> _pendingOAuthConnections;
|
||||
|
||||
std::unordered_map<int, QByteArray> _pendingUploadedContents;
|
||||
std::unordered_map<int, std::unique_ptr<QTemporaryFile>> _pendingContentFiles;
|
||||
|
||||
QThread _assetClientThread;
|
||||
};
|
||||
|
||||
|
|
|
@ -701,9 +701,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.53203323516845703,
|
||||
"x": -0.59333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07286686894893646
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||
"texture": {
|
||||
|
@ -752,7 +752,7 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.59333323516845703,
|
||||
"x": -0.65333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
|
@ -777,9 +777,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.5103323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.127054843521118164
|
||||
"x": -0.5503323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07282185554504395
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||
"texture": {
|
||||
|
@ -1479,9 +1479,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.53203323516845703,
|
||||
"x": -0.59333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07286686894893646
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||
"texture": {
|
||||
|
@ -1530,7 +1530,7 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.59333323516845703,
|
||||
"x": -0.65333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
|
@ -1555,9 +1555,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.5103323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.127054843521118164
|
||||
"x": -0.5503323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07282185554504395
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||
"texture": {
|
||||
|
@ -2305,9 +2305,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.53203323516845703,
|
||||
"x": -0.59333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07286686894893646
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||
"texture": {
|
||||
|
@ -2356,7 +2356,7 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.59333323516845703,
|
||||
"x": -0.65333323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": 0.037454843521118164
|
||||
},
|
||||
|
@ -2381,9 +2381,9 @@
|
|||
"y": 0.04787999764084816
|
||||
},
|
||||
"position": {
|
||||
"x": -0.5103323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.127054843521118164
|
||||
"x": -0.5503323516845703,
|
||||
"y": 0.019300000742077827,
|
||||
"z": -0.07282185554504395
|
||||
},
|
||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||
"texture": {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
window.isKeyboardRaised = false;
|
||||
window.isNumericKeyboard = false;
|
||||
window.isPasswordField = false;
|
||||
window.lastActiveElement = null;
|
||||
window.lastActiveInputElement = null;
|
||||
|
||||
function getActiveElement() {
|
||||
return document.activeElement;
|
||||
|
@ -70,11 +70,15 @@
|
|||
var keyboardRaised = shouldRaiseKeyboard();
|
||||
var numericKeyboard = shouldSetNumeric();
|
||||
var passwordField = shouldSetPasswordField();
|
||||
var activeElement = getActiveElement();
|
||||
var activeInputElement = null;
|
||||
// Only set the active input element when there is an input element focussed, otherwise it will scroll on body focus as well.
|
||||
if (keyboardRaised) {
|
||||
activeInputElement = getActiveElement();
|
||||
}
|
||||
|
||||
if (isWindowFocused &&
|
||||
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
|
||||
|| passwordField !== window.isPasswordField || activeElement !== window.lastActiveElement)) {
|
||||
|| passwordField !== window.isPasswordField || activeInputElement !== window.lastActiveInputElement)) {
|
||||
|
||||
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
|
||||
EventBridge.emitWebEvent(
|
||||
|
@ -96,7 +100,7 @@
|
|||
window.isKeyboardRaised = keyboardRaised;
|
||||
window.isNumericKeyboard = numericKeyboard;
|
||||
window.isPasswordField = passwordField;
|
||||
window.lastActiveElement = activeElement;
|
||||
window.lastActiveInputElement = activeInputElement;
|
||||
}
|
||||
}, POLL_FREQUENCY);
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<script>
|
||||
var handControllerImageURL = null;
|
||||
var index = 0;
|
||||
var count = 5;
|
||||
var count = 3;
|
||||
|
||||
function showKbm() {
|
||||
document.getElementById("main_image").setAttribute("src", "img/tablet-help-keyboard.jpg");
|
||||
|
@ -94,24 +94,14 @@
|
|||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
handControllerImageURL = "img/tablet-help-oculus.jpg";
|
||||
showHandControllers();
|
||||
break;
|
||||
case 1:
|
||||
handControllerImageURL = "img/tablet-help-vive.jpg";
|
||||
showHandControllers();
|
||||
break;
|
||||
case 2:
|
||||
handControllerImageURL = "img/tablet-help-windowsMR.jpg";
|
||||
showHandControllers();
|
||||
break;
|
||||
case 3:
|
||||
showGamepad();
|
||||
break;
|
||||
case 4:
|
||||
case 1:
|
||||
showKbm();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
showHandControllers();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
@ -144,34 +134,33 @@
|
|||
}
|
||||
|
||||
switch (params.handControllerName) {
|
||||
case "oculus":
|
||||
handControllerImageURL = "img/tablet-help-oculus.jpg";
|
||||
index = 0;
|
||||
break;
|
||||
case "windowsMR":
|
||||
handControllerImageURL = "img/tablet-help-windowsMR.jpg";
|
||||
index = 2;
|
||||
break;
|
||||
case "vive":
|
||||
default:
|
||||
handControllerImageURL = "img/tablet-help-vive.jpg";
|
||||
index = 1;
|
||||
break;
|
||||
case "oculus":
|
||||
handControllerImageURL = "img/tablet-help-oculus.jpg";
|
||||
break;
|
||||
default:
|
||||
handControllerImageURL = "";
|
||||
count = 2;
|
||||
}
|
||||
|
||||
switch (params.defaultTab) {
|
||||
case "gamepad":
|
||||
showGamepad();
|
||||
index = 3;
|
||||
break;
|
||||
|
||||
case "handControllers":
|
||||
showHandControllers();
|
||||
index = 2;
|
||||
break;
|
||||
case "gamepad":
|
||||
showGamepad();
|
||||
index = 0;
|
||||
break;
|
||||
|
||||
case "kbm":
|
||||
default:
|
||||
showKbm();
|
||||
index = 4;
|
||||
index = 1;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Binary file not shown.
|
@ -26,6 +26,8 @@ Item {
|
|||
|
||||
property bool interactive: false
|
||||
|
||||
property bool blurOnCtrlShift: true
|
||||
|
||||
StylesUIt.HifiConstants {
|
||||
id: hifi
|
||||
}
|
||||
|
@ -180,8 +182,8 @@ Item {
|
|||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if ((event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
|
||||
webViewCore.focus = false;
|
||||
if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
|
||||
webViewCore.focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ Item {
|
|||
property bool punctuationMode: false
|
||||
property bool passwordField: false
|
||||
property alias flickable: webroot.interactive
|
||||
property alias blurOnCtrlShift: webroot.blurOnCtrlShift
|
||||
|
||||
function stop() {
|
||||
webroot.stop();
|
||||
|
|
|
@ -83,8 +83,10 @@ SpinBox {
|
|||
}
|
||||
|
||||
validator: DoubleValidator {
|
||||
bottom: Math.min(spinBox.from, spinBox.to)
|
||||
top: Math.max(spinBox.from, spinBox.to)
|
||||
decimals: spinBox.decimals
|
||||
bottom: Math.min(spinBox.realFrom, spinBox.realTo)
|
||||
top: Math.max(spinBox.realFrom, spinBox.realTo)
|
||||
notation: DoubleValidator.StandardNotation
|
||||
}
|
||||
|
||||
textFromValue: function(value, locale) {
|
||||
|
@ -97,20 +99,37 @@ SpinBox {
|
|||
|
||||
|
||||
contentItem: TextInput {
|
||||
id: spinboxText
|
||||
z: 2
|
||||
color: isLightColorScheme
|
||||
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
selectedTextColor: hifi.colors.black
|
||||
selectionColor: hifi.colors.primaryHighlight
|
||||
text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix
|
||||
text: spinBox.textFromValue(spinBox.value, spinBox.locale)
|
||||
inputMethodHints: spinBox.inputMethodHints
|
||||
validator: spinBox.validator
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding
|
||||
//rightPadding: hifi.dimensions.spinnerSize
|
||||
width: spinBox.width - hifi.dimensions.spinnerSize
|
||||
onEditingFinished: spinBox.editingFinished()
|
||||
|
||||
Text {
|
||||
id: suffixText
|
||||
x: metrics.advanceWidth(spinboxText.text + '*')
|
||||
height: spinboxText.height
|
||||
|
||||
FontMetrics {
|
||||
id: metrics
|
||||
font: spinboxText.font
|
||||
}
|
||||
|
||||
color: isLightColorScheme
|
||||
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
text: suffix
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
up.indicator: Item {
|
||||
|
|
|
@ -141,6 +141,7 @@ TabletModalWindow {
|
|||
|
||||
Component.onDestruction: {
|
||||
loginKeyboard.raised = false;
|
||||
KeyboardScriptingInterface.raised = false;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
|
|
@ -49,7 +49,6 @@ Item {
|
|||
property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
|
||||
property int shadowHeight: 10;
|
||||
property bool hovered: false
|
||||
property bool scrolling: false
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
|
@ -238,31 +237,38 @@ Item {
|
|||
property var unhoverThunk: function () { };
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: root.hovered && !root.scrolling
|
||||
visible: root.hovered
|
||||
color: "transparent"
|
||||
border.width: 4
|
||||
border.color: hifiStyleConstants.colors.primaryHighlight
|
||||
z: 1
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
acceptedButtons: Qt.LeftButton;
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onContainsMouseChanged: {
|
||||
// Use onContainsMouseChanged rather than onEntered and onExited because the latter aren't always
|
||||
// triggered correctly - e.g., if drag rightwards from right hand side of a card to the next card
|
||||
// onExited doesn't fire, in which case can end up with two cards highlighted.
|
||||
if (containsMouse) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
hoverThunk();
|
||||
} else {
|
||||
unhoverThunk();
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
// Separate MouseArea for click handling so that it doesn't interfere with hovering and interaction
|
||||
// with containing ListView.
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: false
|
||||
onClicked: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
goFunction("hifi://" + hifiUrl);
|
||||
}
|
||||
hoverEnabled: true;
|
||||
onEntered: {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
hoverThunk();
|
||||
}
|
||||
onExited: unhoverThunk();
|
||||
onCanceled: unhoverThunk();
|
||||
}
|
||||
MouseArea {
|
||||
// This second mouse area causes onEntered to fire on the first if you scroll just a little and the cursor stays on
|
||||
// the original card. I.e., the original card is re-highlighted if the cursor is on it after scrolling finishes.
|
||||
anchors.fill: parent
|
||||
}
|
||||
StateImage {
|
||||
id: actionIcon;
|
||||
|
|
|
@ -70,8 +70,8 @@ OriginalDesktop.Desktop {
|
|||
anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined;
|
||||
// Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained.
|
||||
x: sysToolbar.x
|
||||
buttonModel: tablet ? tablet.buttons : null;
|
||||
shown: tablet ? tablet.toolbarMode : false;
|
||||
buttonModel: tablet.buttons;
|
||||
shown: tablet.toolbarMode;
|
||||
}
|
||||
|
||||
Settings {
|
||||
|
|
|
@ -141,7 +141,6 @@ Column {
|
|||
textSizeSmall: root.textSizeSmall;
|
||||
stackShadowNarrowing: root.stackShadowNarrowing;
|
||||
shadowHeight: root.stackedCardShadowHeight;
|
||||
scrolling: scroll.moving
|
||||
|
||||
hoverThunk: function () {
|
||||
hovered = true;
|
||||
|
|
|
@ -256,7 +256,7 @@ Rectangle {
|
|||
color: hifi.colors.baseGrayHighlight;
|
||||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
text: "Wallet";
|
||||
text: "Secure Transactions";
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 20;
|
||||
color: hifi.colors.white;
|
||||
|
@ -287,7 +287,7 @@ Rectangle {
|
|||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
id: securityPictureText;
|
||||
text: "Wallet Security Picture";
|
||||
text: "Security Picture";
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.settings 1.0
|
||||
import stylesUit 1.0
|
||||
|
@ -72,6 +73,11 @@ Item {
|
|||
initialItem: inputConfiguration
|
||||
property alias messageVisible: imageMessageBox.visible
|
||||
property string selectedPlugin: ""
|
||||
|
||||
property bool keyboardEnabled: false
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
Rectangle {
|
||||
id: inputConfiguration
|
||||
anchors {
|
||||
|
@ -227,6 +233,8 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.top: inputConfiguration.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: keyboard.height
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
asynchronous: false
|
||||
|
@ -248,6 +256,29 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
onRaisedChanged: {
|
||||
if (raised) {
|
||||
// delayed execution to allow loader and its content to adjust size
|
||||
Qt.callLater(function() {
|
||||
loader.item.bringToView(Window.activeFocusItem);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
parent.keyboardEnabled = HMD.active;
|
||||
}
|
||||
}
|
||||
|
||||
function inputPlugins() {
|
||||
if (checkBox.checked) {
|
||||
|
|
|
@ -12,4 +12,5 @@ WebView {
|
|||
id: entityListToolWebView
|
||||
url: Paths.defaultScripts + "/system/html/entityList.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
|
|
|
@ -245,6 +245,7 @@ TabBar {
|
|||
id: entityListToolWebView
|
||||
url: Paths.defaultScripts + "/system/html/entityList.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,6 +261,7 @@ TabBar {
|
|||
id: entityPropertiesWebView
|
||||
url: Paths.defaultScripts + "/system/html/entityProperties.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +277,7 @@ TabBar {
|
|||
id: gridControlsWebView
|
||||
url: Paths.defaultScripts + "/system/html/gridControls.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -251,6 +251,7 @@ TabBar {
|
|||
id: entityPropertiesWebView
|
||||
url: Paths.defaultScripts + "/system/html/entityProperties.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +267,7 @@ TabBar {
|
|||
id: gridControlsWebView
|
||||
url: Paths.defaultScripts + "/system/html/gridControls.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ WebView {
|
|||
id: entityListToolWebView
|
||||
url: Paths.defaultScripts + "/system/html/entityList.html"
|
||||
enabled: true
|
||||
blurOnCtrlShift: false
|
||||
}
|
||||
|
|
|
@ -32,6 +32,18 @@ Flickable {
|
|||
}
|
||||
}
|
||||
|
||||
function bringToView(item) {
|
||||
var yTop = item.mapToItem(contentItem, 0, 0).y;
|
||||
var yBottom = yTop + item.height;
|
||||
|
||||
var surfaceTop = contentY;
|
||||
var surfaceBottom = contentY + height;
|
||||
|
||||
if(yTop < surfaceTop || yBottom > surfaceBottom) {
|
||||
contentY = yTop - height / 2 + item.height
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
page = config.createObject(flick.contentItem);
|
||||
}
|
||||
|
@ -39,6 +51,8 @@ Flickable {
|
|||
id: config
|
||||
Rectangle {
|
||||
id: openVrConfiguration
|
||||
anchors.fill: parent
|
||||
|
||||
property int leftMargin: 75
|
||||
property int countDown: 0
|
||||
property string pluginName: ""
|
||||
|
@ -200,6 +214,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,6 +232,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,6 +325,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,6 +342,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -550,13 +568,15 @@ Flickable {
|
|||
width: 160
|
||||
suffix: " cm"
|
||||
label: "Arm Circumference"
|
||||
minimumValue: 0
|
||||
minimumValue: 10.0
|
||||
maximumValue: 50.0
|
||||
realStepSize: 1.0
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
realValue: 33.0
|
||||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,7 +585,8 @@ Flickable {
|
|||
width: 160
|
||||
label: "Shoulder Width"
|
||||
suffix: " cm"
|
||||
minimumValue: 0
|
||||
minimumValue: 25.0
|
||||
maximumValue: 50.0
|
||||
realStepSize: 1.0
|
||||
decimals: 1
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
@ -573,6 +594,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -743,14 +765,17 @@ Flickable {
|
|||
anchors.left: parent.left
|
||||
anchors.leftMargin: leftMargin
|
||||
|
||||
minimumValue: 5
|
||||
minimumValue: 0
|
||||
maximumValue: 5
|
||||
realValue: 5
|
||||
realStepSize: 1.0
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
onEditingFinished: {
|
||||
calibrationTimer.interval = realValue * 1000;
|
||||
openVrConfiguration.countDown = realValue;
|
||||
numberAnimation.duration = calibrationTimer.interval;
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ StackView {
|
|||
Qt.callLater(function() {
|
||||
addressBarDialog.keyboardEnabled = HMD.active;
|
||||
addressLine.forceActiveFocus();
|
||||
addressBarDialog.raised = true;
|
||||
addressBarDialog.keyboardRaised = true;
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -115,9 +115,9 @@ Item {
|
|||
property int previousIndex: -1
|
||||
Repeater {
|
||||
id: pageRepeater
|
||||
model: tabletProxy != null ? Math.ceil(tabletProxy.buttons.rowCount() / TabletEnums.ButtonsOnPage) : 0
|
||||
model: Math.ceil(tabletProxy.buttons.rowCount() / TabletEnums.ButtonsOnPage)
|
||||
onItemAdded: {
|
||||
item.proxyModel.sourceModel = tabletProxy != null ? tabletProxy.buttons : null;
|
||||
item.proxyModel.sourceModel = tabletProxy.buttons;
|
||||
item.proxyModel.pageIndex = index;
|
||||
}
|
||||
|
||||
|
|
BIN
interface/resources/sounds/keyboardPress.mp3
Normal file
BIN
interface/resources/sounds/keyboardPress.mp3
Normal file
Binary file not shown.
Binary file not shown.
|
@ -371,6 +371,8 @@ static const QString INFO_HELP_PATH = "html/tabletHelp.html";
|
|||
|
||||
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
|
||||
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
|
||||
static const int ENTITY_SERVER_ADDED_TIMEOUT = 5000;
|
||||
static const int ENTITY_SERVER_CONNECTION_TIMEOUT = 5000;
|
||||
|
||||
static const uint32_t INVALID_FRAME = UINT32_MAX;
|
||||
|
||||
|
@ -1229,6 +1231,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
getOverlays().deleteOverlay(getTabletScreenID());
|
||||
getOverlays().deleteOverlay(getTabletHomeButtonID());
|
||||
getOverlays().deleteOverlay(getTabletFrameID());
|
||||
_failedToConnectToEntityServer = false;
|
||||
});
|
||||
|
||||
_entityServerConnectionTimer.setSingleShot(true);
|
||||
connect(&_entityServerConnectionTimer, &QTimer::timeout, this, &Application::setFailedToConnectToEntityServer);
|
||||
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, [this]() {
|
||||
if (!isServerlessMode()) {
|
||||
_entityServerConnectionTimer.setInterval(ENTITY_SERVER_ADDED_TIMEOUT);
|
||||
_entityServerConnectionTimer.start();
|
||||
_failedToConnectToEntityServer = false;
|
||||
}
|
||||
});
|
||||
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
|
||||
|
||||
|
@ -3417,26 +3431,37 @@ void Application::showHelp() {
|
|||
static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus";
|
||||
static const QString HAND_CONTROLLER_NAME_WINDOWS_MR = "windowsMR";
|
||||
|
||||
static const QString VIVE_PLUGIN_NAME = "HTC Vive";
|
||||
static const QString OCULUS_RIFT_PLUGIN_NAME = "Oculus Rift";
|
||||
static const QString WINDOWS_MR_PLUGIN_NAME = "WindowsMR";
|
||||
|
||||
static const QString TAB_KEYBOARD_MOUSE = "kbm";
|
||||
static const QString TAB_GAMEPAD = "gamepad";
|
||||
static const QString TAB_HAND_CONTROLLERS = "handControllers";
|
||||
|
||||
QString handControllerName = HAND_CONTROLLER_NAME_VIVE;
|
||||
QString handControllerName;
|
||||
QString defaultTab = TAB_KEYBOARD_MOUSE;
|
||||
|
||||
if (PluginUtils::isViveControllerAvailable()) {
|
||||
defaultTab = TAB_HAND_CONTROLLERS;
|
||||
handControllerName = HAND_CONTROLLER_NAME_VIVE;
|
||||
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||
defaultTab = TAB_HAND_CONTROLLERS;
|
||||
handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH;
|
||||
} else if (qApp->getActiveDisplayPlugin()->getName() == "WindowMS") {
|
||||
if (PluginUtils::isHMDAvailable(WINDOWS_MR_PLUGIN_NAME)) {
|
||||
defaultTab = TAB_HAND_CONTROLLERS;
|
||||
handControllerName = HAND_CONTROLLER_NAME_WINDOWS_MR;
|
||||
} else if (PluginUtils::isHMDAvailable(VIVE_PLUGIN_NAME)) {
|
||||
defaultTab = TAB_HAND_CONTROLLERS;
|
||||
handControllerName = HAND_CONTROLLER_NAME_VIVE;
|
||||
} else if (PluginUtils::isHMDAvailable(OCULUS_RIFT_PLUGIN_NAME)) {
|
||||
if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||
defaultTab = TAB_HAND_CONTROLLERS;
|
||||
handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH;
|
||||
} else if (PluginUtils::isXboxControllerAvailable()) {
|
||||
defaultTab = TAB_GAMEPAD;
|
||||
} else {
|
||||
defaultTab = TAB_KEYBOARD_MOUSE;
|
||||
}
|
||||
} else if (PluginUtils::isXboxControllerAvailable()) {
|
||||
defaultTab = TAB_GAMEPAD;
|
||||
} else {
|
||||
defaultTab = TAB_KEYBOARD_MOUSE;
|
||||
}
|
||||
// TODO need some way to detect windowsMR to load controls reference default tab in Help > Controls Reference menu.
|
||||
|
||||
QUrlQuery queryString;
|
||||
queryString.addQueryItem("handControllerName", handControllerName);
|
||||
|
@ -4039,21 +4064,22 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
break;
|
||||
|
||||
case Qt::Key_P: {
|
||||
AudioInjectorOptions options;
|
||||
options.localOnly = true;
|
||||
options.stereo = true;
|
||||
Setting::Handle<bool> notificationSounds{ MenuOption::NotificationSounds, true};
|
||||
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true};
|
||||
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
|
||||
if (_snapshotSoundInjector) {
|
||||
_snapshotSoundInjector->setOptions(options);
|
||||
_snapshotSoundInjector->restart();
|
||||
} else {
|
||||
QByteArray samples = _snapshotSound->getByteArray();
|
||||
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
|
||||
if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) {
|
||||
AudioInjectorOptions options;
|
||||
options.localOnly = true;
|
||||
options.stereo = true;
|
||||
Setting::Handle<bool> notificationSounds{ MenuOption::NotificationSounds, true };
|
||||
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true };
|
||||
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
|
||||
if (_snapshotSoundInjector) {
|
||||
_snapshotSoundInjector->setOptions(options);
|
||||
_snapshotSoundInjector->restart();
|
||||
} else {
|
||||
_snapshotSoundInjector = AudioInjector::playSound(_snapshotSound, options);
|
||||
}
|
||||
}
|
||||
takeSnapshot(true);
|
||||
}
|
||||
takeSnapshot(true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -4157,6 +4183,10 @@ void Application::focusOutEvent(QFocusEvent* event) {
|
|||
SpacemouseManager::getInstance().ManagerFocusOutEvent();
|
||||
#endif
|
||||
|
||||
synthesizeKeyReleasEvents();
|
||||
}
|
||||
|
||||
void Application::synthesizeKeyReleasEvents() {
|
||||
// synthesize events for keys currently pressed, since we may not get their release events
|
||||
// Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy,
|
||||
// clearing the existing list.
|
||||
|
@ -4782,6 +4812,7 @@ void Application::idle() {
|
|||
if (_keyboardDeviceHasFocus && activeFocusItem != offscreenUi->getRootItem()) {
|
||||
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||
_keyboardDeviceHasFocus = false;
|
||||
synthesizeKeyReleasEvents();
|
||||
} else if (activeFocusItem == offscreenUi->getRootItem()) {
|
||||
_keyboardDeviceHasFocus = true;
|
||||
}
|
||||
|
@ -5726,6 +5757,7 @@ void Application::update(float deltaTime) {
|
|||
quint64 now = usecTimestampNow();
|
||||
if (isServerlessMode() || _octreeProcessor.isLoadSequenceComplete()) {
|
||||
bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
|
||||
|
||||
if (gpuTextureMemSizeStable() || !enableInterstitial) {
|
||||
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
|
||||
_lastPhysicsCheckTime = now;
|
||||
|
@ -6657,7 +6689,13 @@ void Application::resettingDomain() {
|
|||
}
|
||||
|
||||
void Application::nodeAdded(SharedNodePointer node) const {
|
||||
// nothing to do here
|
||||
if (node->getType() == NodeType::EntityServer) {
|
||||
if (!_failedToConnectToEntityServer) {
|
||||
_entityServerConnectionTimer.stop();
|
||||
_entityServerConnectionTimer.setInterval(ENTITY_SERVER_CONNECTION_TIMEOUT);
|
||||
_entityServerConnectionTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::nodeActivated(SharedNodePointer node) {
|
||||
|
@ -6685,6 +6723,10 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
if (node->getType() == NodeType::EntityServer) {
|
||||
_queryExpiry = SteadyClock::now();
|
||||
_octreeQuery.incrementConnectionID();
|
||||
|
||||
if (!_failedToConnectToEntityServer) {
|
||||
_entityServerConnectionTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) {
|
||||
|
|
|
@ -310,6 +310,7 @@ public:
|
|||
|
||||
bool isServerlessMode() const;
|
||||
bool isInterstitialMode() const { return _interstitialMode; }
|
||||
bool failedToConnectToEntityServer() const { return _failedToConnectToEntityServer; }
|
||||
|
||||
void replaceDomainContent(const QString& url);
|
||||
|
||||
|
@ -467,6 +468,7 @@ private slots:
|
|||
|
||||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
void setFailedToConnectToEntityServer() { _failedToConnectToEntityServer = true; }
|
||||
|
||||
bool acceptSnapshot(const QString& urlString);
|
||||
bool askToSetAvatarUrl(const QString& url);
|
||||
|
@ -550,6 +552,7 @@ private:
|
|||
void keyReleaseEvent(QKeyEvent* event);
|
||||
|
||||
void focusOutEvent(QFocusEvent* event);
|
||||
void synthesizeKeyReleasEvents();
|
||||
void focusInEvent(QFocusEvent* event);
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
|
@ -718,6 +721,7 @@ private:
|
|||
bool _isForeground = true; // starts out assumed to be in foreground
|
||||
bool _isGLInitialized { false };
|
||||
bool _physicsEnabled { false };
|
||||
bool _failedToConnectToEntityServer { false };
|
||||
|
||||
bool _reticleClickPressed { false };
|
||||
|
||||
|
@ -764,6 +768,7 @@ private:
|
|||
QStringList _addAssetToWorldInfoMessages; // Info message
|
||||
QTimer _addAssetToWorldInfoTimer;
|
||||
QTimer _addAssetToWorldErrorTimer;
|
||||
mutable QTimer _entityServerConnectionTimer;
|
||||
|
||||
FileScriptingInterface* _fileDownload;
|
||||
AudioInjectorPointer _snapshotSoundInjector;
|
||||
|
|
|
@ -141,7 +141,7 @@ Menu::Menu() {
|
|||
assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
|
||||
}
|
||||
|
||||
// Edit > Package Model as .fst...
|
||||
// Edit > Package Avatar as .fst...
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
|
||||
qApp, SLOT(packageModel()));
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace MenuOption {
|
|||
const QString OpenVrThreadedSubmit = "OpenVR Threaded Submit";
|
||||
const QString OutputMenu = "Display";
|
||||
const QString Overlays = "Show Overlays";
|
||||
const QString PackageModel = "Package Model as .fst...";
|
||||
const QString PackageModel = "Package Avatar as .fst...";
|
||||
const QString Pair = "Pair";
|
||||
const QString PhysicsShowOwned = "Highlight Simulation Ownership";
|
||||
const QString VerboseLogging = "Verbose Logging";
|
||||
|
|
|
@ -69,7 +69,6 @@ bool ModelPackager::selectModel() {
|
|||
ModelSelector selector;
|
||||
if(selector.exec() == QDialog::Accepted) {
|
||||
_modelFile = selector.getFileInfo();
|
||||
_modelType = selector.getModelType();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -123,28 +122,26 @@ bool ModelPackager::loadModel() {
|
|||
|
||||
bool ModelPackager::editProperties() {
|
||||
// open the dialog to configure the rest
|
||||
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_hfmModel);
|
||||
ModelPropertiesDialog properties(_mapping, _modelFile.path(), *_hfmModel);
|
||||
if (properties.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
_mapping = properties.getMapping();
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
// Make sure that a mapping for the root joint has been specified
|
||||
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointRoot")) {
|
||||
qWarning() << "root joint not configured for skeleton.";
|
||||
// Make sure that a mapping for the root joint has been specified
|
||||
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointRoot")) {
|
||||
qWarning() << "root joint not configured for skeleton.";
|
||||
|
||||
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Model Packager");
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Model Packager");
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -238,8 +235,6 @@ bool ModelPackager::zipModel() {
|
|||
|
||||
void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const hfm::Model& hfmModel) {
|
||||
|
||||
bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL;
|
||||
|
||||
// mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
|
||||
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
|
||||
bool likelyMixamoFile = hfmModel.applicationName == "mixamo.com" ||
|
||||
|
@ -280,19 +275,17 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
joints.insert("jointNeck", hfmModel.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
|
||||
}
|
||||
|
||||
if (isBodyType) {
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
|
||||
if (!joints.contains("jointHead")) {
|
||||
|
@ -302,13 +295,11 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
|
||||
if (isBodyType) {
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
}
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
}
|
||||
|
||||
// If there are no blendshape mappings, and we detect that this is likely a mixamo file,
|
||||
|
|
|
@ -41,7 +41,6 @@ private:
|
|||
|
||||
QFileInfo _modelFile;
|
||||
QFileInfo _fbxInfo;
|
||||
FSTReader::ModelType _modelType;
|
||||
QString _texDir;
|
||||
QString _scriptDir;
|
||||
|
||||
|
|
|
@ -26,9 +26,8 @@
|
|||
#include <OffscreenUi.h>
|
||||
|
||||
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(const QVariantHash& originalMapping,
|
||||
const QString& basePath, const HFMModel& hfmModel) :
|
||||
_modelType(modelType),
|
||||
_originalMapping(originalMapping),
|
||||
_basePath(basePath),
|
||||
_hfmModel(hfmModel)
|
||||
|
@ -50,36 +49,19 @@ _hfmModel(hfmModel)
|
|||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setSingleStep(0.01);
|
||||
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
QHBoxLayout* translation = new QHBoxLayout();
|
||||
form->addRow("Translation:", translation);
|
||||
translation->addWidget(_translationX = createTranslationBox());
|
||||
translation->addWidget(_translationY = createTranslationBox());
|
||||
translation->addWidget(_translationZ = createTranslationBox());
|
||||
form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox());
|
||||
form->addRow("Pivot Joint:", _pivotJoint = createJointBox());
|
||||
connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint()));
|
||||
_pivotAboutCenter->setChecked(true);
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
||||
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
||||
|
||||
} else {
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
}
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
||||
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
||||
|
||||
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
||||
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
||||
_freeJoints->addWidget(newFreeJoint);
|
||||
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
||||
}
|
||||
}
|
||||
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
||||
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
||||
_freeJoints->addWidget(newFreeJoint);
|
||||
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel | QDialogButtonBox::Reset);
|
||||
|
@ -93,14 +75,9 @@ _hfmModel(hfmModel)
|
|||
reset();
|
||||
}
|
||||
|
||||
|
||||
QString ModelPropertiesDialog::getType() const {
|
||||
return FSTReader::getNameFromType(_modelType);
|
||||
}
|
||||
|
||||
QVariantHash ModelPropertiesDialog::getMapping() const {
|
||||
QVariantHash mapping = _originalMapping;
|
||||
mapping.insert(TYPE_FIELD, getType());
|
||||
mapping.insert(TYPE_FIELD, FSTReader::getNameFromType(FSTReader::HEAD_AND_BODY_MODEL));
|
||||
mapping.insert(NAME_FIELD, _name->text());
|
||||
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
|
||||
mapping.insert(SCRIPT_FIELD, _scriptDirectory->text());
|
||||
|
@ -113,42 +90,24 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
|
|||
}
|
||||
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
||||
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
glm::vec3 pivot;
|
||||
if (_pivotAboutCenter->isChecked()) {
|
||||
pivot = (_hfmModel.meshExtents.minimum + _hfmModel.meshExtents.maximum) * 0.5f;
|
||||
|
||||
} else if (_pivotJoint->currentIndex() != 0) {
|
||||
pivot = extractTranslation(_hfmModel.joints.at(_pivotJoint->currentIndex() - 1).transform);
|
||||
}
|
||||
mapping.insert(TRANSLATION_X_FIELD, -pivot.x * (float)_scale->value() + (float)_translationX->value());
|
||||
mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * (float)_scale->value() + (float)_translationY->value());
|
||||
mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * (float)_scale->value() + (float)_translationZ->value());
|
||||
|
||||
} else {
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
}
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
||||
|
||||
mapping.remove(FREE_JOINT_FIELD);
|
||||
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
||||
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
||||
}
|
||||
}
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
mapping.remove(FREE_JOINT_FIELD);
|
||||
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
||||
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
||||
}
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
@ -165,36 +124,23 @@ void ModelPropertiesDialog::reset() {
|
|||
|
||||
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
||||
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
|
||||
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
|
||||
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
|
||||
_pivotAboutCenter->setChecked(true);
|
||||
_pivotJoint->setCurrentIndex(0);
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
|
||||
} else {
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
}
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
||||
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
||||
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
||||
|
||||
while (_freeJoints->count() > 1) {
|
||||
delete _freeJoints->itemAt(0)->widget();
|
||||
}
|
||||
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
||||
QString jointName = joint.toString();
|
||||
if (_hfmModel.jointIndices.contains(jointName)) {
|
||||
createNewFreeJoint(jointName);
|
||||
}
|
||||
}
|
||||
while (_freeJoints->count() > 1) {
|
||||
delete _freeJoints->itemAt(0)->widget();
|
||||
}
|
||||
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
||||
QString jointName = joint.toString();
|
||||
if (_hfmModel.jointIndices.contains(jointName)) {
|
||||
createNewFreeJoint(jointName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class ModelPropertiesDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog(const QVariantHash& originalMapping,
|
||||
const QString& basePath, const HFMModel& hfmModel);
|
||||
|
||||
QVariantHash getMapping() const;
|
||||
|
@ -45,9 +45,7 @@ private:
|
|||
QComboBox* createJointBox(bool withNone = true) const;
|
||||
QDoubleSpinBox* createTranslationBox() const;
|
||||
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
||||
QString getType() const;
|
||||
|
||||
FSTReader::ModelType _modelType;
|
||||
QVariantHash _originalMapping;
|
||||
QString _basePath;
|
||||
HFMModel _hfmModel;
|
||||
|
@ -71,4 +69,4 @@ private:
|
|||
QVBoxLayout* _freeJoints = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelPropertiesDialog_h
|
||||
#endif // hifi_ModelPropertiesDialog_h
|
||||
|
|
|
@ -27,18 +27,11 @@ ModelSelector::ModelSelector() {
|
|||
|
||||
setWindowTitle("Select Model");
|
||||
setLayout(form);
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
_browseButton = new QPushButton("Browse", this);
|
||||
connect(_browseButton, &QPushButton::clicked, this, &ModelSelector::browse);
|
||||
form->addRow("Model File:", _browseButton);
|
||||
|
||||
_modelType = new QComboBox(this);
|
||||
|
||||
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
|
||||
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
|
||||
_modelType->addItem(ENTITY_MODEL_STRING);
|
||||
form->addRow("Model Type:", _modelType);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ModelSelector::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
@ -49,19 +42,6 @@ QFileInfo ModelSelector::getFileInfo() const {
|
|||
return _modelFile;
|
||||
}
|
||||
|
||||
FSTReader::ModelType ModelSelector::getModelType() const {
|
||||
QString text = _modelType->currentText();
|
||||
|
||||
if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
return FSTReader::HEAD_AND_BODY_MODEL;
|
||||
} else if (text == AVATAR_ATTACHEMENT_STRING) {
|
||||
return FSTReader::ATTACHMENT_MODEL;
|
||||
} else if (text == ENTITY_MODEL_STRING) {
|
||||
return FSTReader::ENTITY_MODEL;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
void ModelSelector::accept() {
|
||||
if (!_modelFile.isFile()) {
|
||||
return;
|
||||
|
|
|
@ -29,7 +29,6 @@ public:
|
|||
ModelSelector();
|
||||
|
||||
QFileInfo getFileInfo() const;
|
||||
FSTReader::ModelType getModelType() const;
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
@ -40,7 +39,6 @@ public:
|
|||
private:
|
||||
QFileInfo _modelFile;
|
||||
QPushButton* _browseButton;
|
||||
QComboBox* _modelType;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelSelector_h
|
||||
|
|
|
@ -562,8 +562,13 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
|||
|
||||
static const int MAX_INJECTOR_COUNT = 3;
|
||||
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
|
||||
auto injector = AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR,
|
||||
myAvatar->getWorldPosition());
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = collisionSound->isStereo();
|
||||
options.position = myAvatar->getWorldPosition();
|
||||
options.volume = energyFactorOfFull;
|
||||
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
|
||||
|
||||
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
_collisionInjectors.emplace_back(injector);
|
||||
}
|
||||
myAvatar->collisionWithEntity(collision);
|
||||
|
|
|
@ -107,7 +107,7 @@ void SafeLanding::noteReceivedsequenceNumber(int sequenceNumber) {
|
|||
}
|
||||
|
||||
bool SafeLanding::isLoadSequenceComplete() {
|
||||
if (isEntityLoadingComplete() && isSequenceNumbersComplete()) {
|
||||
if ((isEntityLoadingComplete() && isSequenceNumbersComplete()) || qApp->failedToConnectToEntityServer()) {
|
||||
Locker lock(_lock);
|
||||
_initialStart = INVALID_SEQUENCE;
|
||||
_initialEnd = INVALID_SEQUENCE;
|
||||
|
|
|
@ -147,7 +147,11 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) {
|
|||
_lastSoundAudioInjectorUpdateTimer.stop();
|
||||
}
|
||||
|
||||
_lastSoundAudioInjector = AudioInjector::playSoundAndDelete(_lastSoundByteArray, options);
|
||||
uint32_t numChannels = 1;
|
||||
uint32_t numSamples = (uint32_t)_lastSoundByteArray.size() / sizeof(AudioData::AudioSample);
|
||||
auto samples = reinterpret_cast<AudioData::AudioSample*>(_lastSoundByteArray.data());
|
||||
auto newAudioData = AudioData::make(numSamples, numChannels, samples);
|
||||
_lastSoundAudioInjector = AudioInjector::playSoundAndDelete(newAudioData, options);
|
||||
|
||||
_lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
|
||||
#else
|
||||
|
|
|
@ -198,4 +198,14 @@ void TestScriptingInterface::setOtherAvatarsReplicaCount(int count) {
|
|||
|
||||
int TestScriptingInterface::getOtherAvatarsReplicaCount() {
|
||||
return qApp->getOtherAvatarsReplicaCount();
|
||||
}
|
||||
}
|
||||
|
||||
QString TestScriptingInterface::getOperatingSystemType() {
|
||||
#ifdef Q_OS_WIN
|
||||
return "WINDOWS";
|
||||
#elif defined Q_OS_MAC
|
||||
return "MACOS";
|
||||
#else
|
||||
return "UNKNOWN";
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -163,6 +163,13 @@ public slots:
|
|||
*/
|
||||
Q_INVOKABLE int getOtherAvatarsReplicaCount();
|
||||
|
||||
/**jsdoc
|
||||
* Returns the Operating Sytem type
|
||||
* @function Test.getOperatingSystemType
|
||||
* @returns {string} "WINDOWS", "MACOS" or "UNKNOWN"
|
||||
*/
|
||||
QString getOperatingSystemType();
|
||||
|
||||
private:
|
||||
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
||||
QString _testResultsLocation;
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "scripting/WindowScriptingInterface.h"
|
||||
#include "scripting/SelectionScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "DependencyManager.h"
|
||||
|
||||
#include "raypick/StylusPointer.h"
|
||||
|
@ -54,9 +55,9 @@
|
|||
static const int LEFT_HAND_CONTROLLER_INDEX = 0;
|
||||
static const int RIGHT_HAND_CONTROLLER_INDEX = 1;
|
||||
|
||||
static const float MALLET_LENGTH = 0.2f;
|
||||
static const float MALLET_TOUCH_Y_OFFSET = 0.052f;
|
||||
static const float MALLET_Y_OFFSET = 0.180f;
|
||||
static const float MALLET_LENGTH = 0.18f;
|
||||
static const float MALLET_TOUCH_Y_OFFSET = 0.050f;
|
||||
static const float MALLET_Y_OFFSET = 0.160f;
|
||||
|
||||
static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f};
|
||||
static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f};
|
||||
|
@ -65,14 +66,14 @@ static const glm::vec3 MALLET_TIP_OFFSET{0.0f, MALLET_LENGTH - MALLET_TOUCH_Y_OF
|
|||
|
||||
|
||||
static const glm::vec3 Z_AXIS {0.0f, 0.0f, 1.0f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.28f, -0.3f, -0.05f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.30f, -0.38f, -0.04f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f};
|
||||
static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f};
|
||||
static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f};
|
||||
|
||||
static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboard_key.mp3";
|
||||
static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboardPress.mp3";
|
||||
static const QString MALLET_MODEL_URL = PathUtils::resourcesUrl() + "meshes/drumstick.fbx";
|
||||
|
||||
static const float PULSE_STRENGTH = 0.6f;
|
||||
|
@ -221,6 +222,7 @@ Keyboard::Keyboard() {
|
|||
auto pointerManager = DependencyManager::get<PointerManager>();
|
||||
auto windowScriptingInterface = DependencyManager::get<WindowScriptingInterface>();
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
auto hmdScriptingInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Keyboard::handleTriggerBegin, Qt::QueuedConnection);
|
||||
connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Keyboard::handleTriggerContinue, Qt::QueuedConnection);
|
||||
connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Keyboard::handleTriggerEnd, Qt::QueuedConnection);
|
||||
|
@ -228,6 +230,7 @@ Keyboard::Keyboard() {
|
|||
connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Keyboard::handleHoverEnd, Qt::QueuedConnection);
|
||||
connect(myAvatar.get(), &MyAvatar::sensorToWorldScaleChanged, this, &Keyboard::scaleKeyboard, Qt::QueuedConnection);
|
||||
connect(windowScriptingInterface.data(), &WindowScriptingInterface::domainChanged, [&]() { setRaised(false); });
|
||||
connect(hmdScriptingInterface.data(), &HMDScriptingInterface::displayModeChanged, [&]() { setRaised(false); });
|
||||
}
|
||||
|
||||
void Keyboard::registerKeyboardHighlighting() {
|
||||
|
@ -483,9 +486,9 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent
|
|||
AudioInjectorOptions audioOptions;
|
||||
audioOptions.localOnly = true;
|
||||
audioOptions.position = keyWorldPosition;
|
||||
audioOptions.volume = 0.1f;
|
||||
audioOptions.volume = 0.05f;
|
||||
|
||||
AudioInjector::playSound(_keySound->getByteArray(), audioOptions);
|
||||
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
|
||||
|
||||
int scanCode = key.getScanCode(_capsEnabled);
|
||||
QString keyString = key.getKeyString(_capsEnabled);
|
||||
|
@ -835,8 +838,8 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) {
|
|||
_textDisplay = textDisplay;
|
||||
|
||||
_ignoreItemsLock.withWriteLock([&] {
|
||||
_itemsToIgnore.push_back(_textDisplay.overlayID);
|
||||
_itemsToIgnore.push_back(_anchor.overlayID);
|
||||
_itemsToIgnore.append(_textDisplay.overlayID);
|
||||
_itemsToIgnore.append(_anchor.overlayID);
|
||||
});
|
||||
_layerIndex = 0;
|
||||
auto pointerManager = DependencyManager::get<PointerManager>();
|
||||
|
|
|
@ -535,7 +535,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
bool bestIsFront = false;
|
||||
bool bestIsTablet = false;
|
||||
auto tabletIDs = qApp->getTabletIDs();
|
||||
|
||||
const QVector<OverlayID> keyboardKeysToDiscard = DependencyManager::get<Keyboard>()->getKeysID();
|
||||
QMutexLocker locker(&_mutex);
|
||||
RayToOverlayIntersectionResult result;
|
||||
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
|
||||
|
@ -545,7 +545,8 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
|
||||
|
||||
if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) ||
|
||||
(overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) {
|
||||
(overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID)) ||
|
||||
(keyboardKeysToDiscard.size() > 0 && keyboardKeysToDiscard.contains(thisID))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,39 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) {
|
|||
for (auto& joint : hfmModel.joints) {
|
||||
joints.push_back(joint);
|
||||
}
|
||||
buildSkeletonFromJoints(joints);
|
||||
buildSkeletonFromJoints(joints, hfmModel.jointRotationOffsets);
|
||||
|
||||
// we make a copy of the inverseBindMatrices in order to prevent mutating the model bind pose
|
||||
// when we are dealing with a joint offset in the model
|
||||
for (int i = 0; i < (int)hfmModel.meshes.size(); i++) {
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
std::vector<HFMCluster> dummyClustersList;
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
std::vector<glm::mat4> bindMatrices;
|
||||
// cast into a non-const reference, so we can mutate the FBXCluster
|
||||
HFMCluster& cluster = const_cast<HFMCluster&>(mesh.clusters.at(j));
|
||||
|
||||
HFMCluster localCluster;
|
||||
localCluster.jointIndex = cluster.jointIndex;
|
||||
localCluster.inverseBindMatrix = cluster.inverseBindMatrix;
|
||||
localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix);
|
||||
|
||||
// if we have a joint offset in the fst file then multiply its inverse by the
|
||||
// model cluster inverse bind matrix
|
||||
if (hfmModel.jointRotationOffsets.contains(cluster.jointIndex)) {
|
||||
AnimPose localOffset(hfmModel.jointRotationOffsets[cluster.jointIndex], glm::vec3());
|
||||
localCluster.inverseBindMatrix = (glm::mat4)localOffset.inverse() * cluster.inverseBindMatrix;
|
||||
localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix);
|
||||
}
|
||||
dummyClustersList.push_back(localCluster);
|
||||
}
|
||||
_clusterBindMatrixOriginalValues.push_back(dummyClustersList);
|
||||
}
|
||||
}
|
||||
|
||||
AnimSkeleton::AnimSkeleton(const std::vector<HFMJoint>& joints) {
|
||||
buildSkeletonFromJoints(joints);
|
||||
AnimSkeleton::AnimSkeleton(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets) {
|
||||
buildSkeletonFromJoints(joints, jointOffsets);
|
||||
}
|
||||
|
||||
int AnimSkeleton::nameToJointIndex(const QString& jointName) const {
|
||||
|
@ -166,7 +194,8 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const {
|
|||
}
|
||||
}
|
||||
|
||||
void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints) {
|
||||
void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets) {
|
||||
|
||||
_joints = joints;
|
||||
_jointsSize = (int)joints.size();
|
||||
// build a cache of bind poses
|
||||
|
@ -189,7 +218,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints)
|
|||
// build relative and absolute default poses
|
||||
glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform;
|
||||
AnimPose relDefaultPose(relDefaultMat);
|
||||
_relativeDefaultPoses.push_back(relDefaultPose);
|
||||
|
||||
int parentIndex = getParentIndex(i);
|
||||
if (parentIndex >= 0) {
|
||||
_absoluteDefaultPoses.push_back(_absoluteDefaultPoses[parentIndex] * relDefaultPose);
|
||||
|
@ -198,6 +227,16 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints)
|
|||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < _jointsSize; k++) {
|
||||
if (jointOffsets.contains(k)) {
|
||||
AnimPose localOffset(jointOffsets[k], glm::vec3());
|
||||
_absoluteDefaultPoses[k] = _absoluteDefaultPoses[k] * localOffset;
|
||||
}
|
||||
}
|
||||
// re-compute relative poses
|
||||
_relativeDefaultPoses = _absoluteDefaultPoses;
|
||||
convertAbsolutePosesToRelative(_relativeDefaultPoses);
|
||||
|
||||
for (int i = 0; i < _jointsSize; i++) {
|
||||
_jointIndicesByName[_joints[i].name] = i;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ public:
|
|||
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
|
||||
|
||||
explicit AnimSkeleton(const HFMModel& hfmModel);
|
||||
explicit AnimSkeleton(const std::vector<HFMJoint>& joints);
|
||||
explicit AnimSkeleton(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets);
|
||||
|
||||
int nameToJointIndex(const QString& jointName) const;
|
||||
const QString& getJointName(int jointIndex) const;
|
||||
int getNumJoints() const;
|
||||
|
@ -62,9 +63,10 @@ public:
|
|||
void dump(const AnimPoseVec& poses) const;
|
||||
|
||||
std::vector<int> lookUpJointIndices(const std::vector<QString>& jointNames) const;
|
||||
const HFMCluster getClusterBindMatricesOriginalValues(const int meshIndex, const int clusterIndex) const { return _clusterBindMatrixOriginalValues[meshIndex][clusterIndex]; }
|
||||
|
||||
protected:
|
||||
void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints);
|
||||
void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets);
|
||||
|
||||
std::vector<HFMJoint> _joints;
|
||||
int _jointsSize { 0 };
|
||||
|
@ -76,6 +78,7 @@ protected:
|
|||
std::vector<int> _nonMirroredIndices;
|
||||
std::vector<int> _mirrorMap;
|
||||
QHash<QString, int> _jointIndicesByName;
|
||||
std::vector<std::vector<HFMCluster>> _clusterBindMatrixOriginalValues;
|
||||
|
||||
// no copies
|
||||
AnimSkeleton(const AnimSkeleton&) = delete;
|
||||
|
|
|
@ -360,8 +360,10 @@ void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset
|
|||
void Rig::reset(const HFMModel& hfmModel) {
|
||||
_geometryOffset = AnimPose(hfmModel.offset);
|
||||
_invGeometryOffset = _geometryOffset.inverse();
|
||||
|
||||
_animSkeleton = std::make_shared<AnimSkeleton>(hfmModel);
|
||||
|
||||
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace AudioConstants {
|
|||
const int STEREO = 2;
|
||||
const int AMBISONIC = 4;
|
||||
|
||||
typedef int16_t AudioSample;
|
||||
using AudioSample = int16_t;
|
||||
const int SAMPLE_SIZE = sizeof(AudioSample);
|
||||
|
||||
inline const char* getAudioFrameName() { return "com.highfidelity.recording.Audio"; }
|
||||
|
|
|
@ -38,12 +38,14 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs)
|
|||
return lhs;
|
||||
};
|
||||
|
||||
AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions) :
|
||||
AudioInjector(sound.getByteArray(), injectorOptions)
|
||||
AudioInjector::AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) :
|
||||
_sound(sound),
|
||||
_audioData(sound->getAudioData()),
|
||||
_options(injectorOptions)
|
||||
{
|
||||
}
|
||||
|
||||
AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions) :
|
||||
AudioInjector::AudioInjector(AudioDataPointer audioData, const AudioInjectorOptions& injectorOptions) :
|
||||
_audioData(audioData),
|
||||
_options(injectorOptions)
|
||||
{
|
||||
|
@ -154,7 +156,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj
|
|||
bool AudioInjector::injectLocally() {
|
||||
bool success = false;
|
||||
if (_localAudioInterface) {
|
||||
if (_audioData.size() > 0) {
|
||||
if (_audioData->getNumBytes() > 0) {
|
||||
|
||||
_localBuffer = new AudioInjectorLocalBuffer(_audioData);
|
||||
|
||||
|
@ -220,22 +222,12 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
|
||||
if (!_currentPacket) {
|
||||
if (_currentSendOffset < 0 ||
|
||||
_currentSendOffset >= _audioData.size()) {
|
||||
_currentSendOffset >= (int)_audioData->getNumBytes()) {
|
||||
_currentSendOffset = 0;
|
||||
}
|
||||
|
||||
// make sure we actually have samples downloaded to inject
|
||||
if (_audioData.size()) {
|
||||
|
||||
int sampleSize = (_options.stereo ? 2 : 1) * sizeof(AudioConstants::AudioSample);
|
||||
auto numSamples = static_cast<int>(_audioData.size() / sampleSize);
|
||||
auto targetSize = numSamples * sampleSize;
|
||||
if (targetSize != _audioData.size()) {
|
||||
qCDebug(audio) << "Resizing audio that doesn't end at multiple of sample size, resizing from "
|
||||
<< _audioData.size() << " to " << targetSize;
|
||||
_audioData.resize(targetSize);
|
||||
}
|
||||
|
||||
if (_audioData && _audioData->getNumSamples() > 0) {
|
||||
_outgoingSequenceNumber = 0;
|
||||
_nextFrame = 0;
|
||||
|
||||
|
@ -307,19 +299,10 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
_frameTimer->restart();
|
||||
}
|
||||
|
||||
int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
if (!_options.loop) {
|
||||
// If we aren't looping, let's make sure we don't read past the end
|
||||
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset);
|
||||
}
|
||||
|
||||
// Measure the loudness of this frame
|
||||
_loudness = 0.0f;
|
||||
for (int i = 0; i < totalBytesLeftToCopy; i += sizeof(int16_t)) {
|
||||
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + ((_currentSendOffset + i) % _audioData.size()))) /
|
||||
(AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
||||
}
|
||||
_loudness /= (float)(totalBytesLeftToCopy/ sizeof(int16_t));
|
||||
assert(loopbackOptionOffset != -1);
|
||||
assert(positionOptionOffset != -1);
|
||||
assert(volumeOptionOffset != -1);
|
||||
assert(audioDataOffset != -1);
|
||||
|
||||
_currentPacket->seek(0);
|
||||
|
||||
|
@ -339,19 +322,37 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
|
||||
_currentPacket->seek(audioDataOffset);
|
||||
|
||||
// This code is copying bytes from the _audioData directly into the packet, handling looping appropriately.
|
||||
// This code is copying bytes from the _sound directly into the packet, handling looping appropriately.
|
||||
// Might be a reasonable place to do the encode step here.
|
||||
QByteArray decodedAudio;
|
||||
while (totalBytesLeftToCopy > 0) {
|
||||
int bytesToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset);
|
||||
|
||||
decodedAudio.append(_audioData.data() + _currentSendOffset, bytesToCopy);
|
||||
_currentSendOffset += bytesToCopy;
|
||||
totalBytesLeftToCopy -= bytesToCopy;
|
||||
if (_options.loop && _currentSendOffset >= _audioData.size()) {
|
||||
_currentSendOffset = 0;
|
||||
}
|
||||
int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
if (!_options.loop) {
|
||||
// If we aren't looping, let's make sure we don't read past the end
|
||||
int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset;
|
||||
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead);
|
||||
}
|
||||
|
||||
auto samples = _audioData->data();
|
||||
auto currentSample = _currentSendOffset / AudioConstants::SAMPLE_SIZE;
|
||||
auto samplesLeftToCopy = totalBytesLeftToCopy / AudioConstants::SAMPLE_SIZE;
|
||||
|
||||
using AudioConstants::AudioSample;
|
||||
decodedAudio.resize(totalBytesLeftToCopy);
|
||||
auto samplesOut = reinterpret_cast<AudioSample*>(decodedAudio.data());
|
||||
|
||||
// Copy and Measure the loudness of this frame
|
||||
_loudness = 0.0f;
|
||||
for (int i = 0; i < samplesLeftToCopy; ++i) {
|
||||
auto index = (currentSample + i) % _audioData->getNumSamples();
|
||||
auto sample = samples[index];
|
||||
samplesOut[i] = sample;
|
||||
_loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
||||
}
|
||||
_loudness /= (float)samplesLeftToCopy;
|
||||
_currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) %
|
||||
_audioData->getNumBytes();
|
||||
|
||||
// FIXME -- good place to call codec encode here. We need to figure out how to tell the AudioInjector which
|
||||
// codec to use... possible through AbstractAudioInterface.
|
||||
QByteArray encodedAudio = decodedAudio;
|
||||
|
@ -370,7 +371,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
_outgoingSequenceNumber++;
|
||||
}
|
||||
|
||||
if (_currentSendOffset >= _audioData.size() && !_options.loop) {
|
||||
if (_currentSendOffset == 0 && !_options.loop) {
|
||||
finishNetworkInjection();
|
||||
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
|
||||
}
|
||||
|
@ -390,7 +391,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
// If we are falling behind by more frames than our threshold, let's skip the frames ahead
|
||||
qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames";
|
||||
_nextFrame = currentFrameBasedOnElapsedTime;
|
||||
_currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData.size();
|
||||
_currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData->getNumBytes();
|
||||
}
|
||||
|
||||
int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS;
|
||||
|
@ -417,38 +418,25 @@ void AudioInjector::triggerDeleteAfterFinish() {
|
|||
}
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const float volume,
|
||||
const float stretchFactor, const glm::vec3 position) {
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options) {
|
||||
AudioInjectorPointer injector = playSound(sound, options);
|
||||
|
||||
if (injector) {
|
||||
injector->_state |= AudioInjectorState::PendingDelete;
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const AudioInjectorOptions& options) {
|
||||
if (!sound || !sound->isReady()) {
|
||||
return AudioInjectorPointer();
|
||||
}
|
||||
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = sound->isStereo();
|
||||
options.position = position;
|
||||
options.volume = volume;
|
||||
options.pitch = 1.0f / stretchFactor;
|
||||
|
||||
QByteArray samples = sound->getByteArray();
|
||||
|
||||
return playSoundAndDelete(samples, options);
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||
AudioInjectorPointer sound = playSound(buffer, options);
|
||||
|
||||
if (sound) {
|
||||
sound->_state |= AudioInjectorState::PendingDelete;
|
||||
}
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||
|
||||
if (options.pitch == 1.0f) {
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options);
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(sound, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread injector";
|
||||
|
@ -456,24 +444,31 @@ AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const Au
|
|||
return injector;
|
||||
|
||||
} else {
|
||||
using AudioConstants::AudioSample;
|
||||
using AudioConstants::SAMPLE_RATE;
|
||||
const int standardRate = SAMPLE_RATE;
|
||||
// limit to 4 octaves
|
||||
const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
||||
const int resampledRate = SAMPLE_RATE / pitch;
|
||||
|
||||
const int standardRate = AudioConstants::SAMPLE_RATE;
|
||||
const int resampledRate = AudioConstants::SAMPLE_RATE / glm::clamp(options.pitch, 1/16.0f, 16.0f); // limit to 4 octaves
|
||||
const int numChannels = options.ambisonic ? AudioConstants::AMBISONIC :
|
||||
(options.stereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
auto audioData = sound->getAudioData();
|
||||
auto numChannels = audioData->getNumChannels();
|
||||
auto numFrames = audioData->getNumFrames();
|
||||
|
||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||
|
||||
// create a resampled buffer that is guaranteed to be large enough
|
||||
const int nInputFrames = buffer.size() / (numChannels * sizeof(int16_t));
|
||||
const int maxOutputFrames = resampler.getMaxOutput(nInputFrames);
|
||||
QByteArray resampledBuffer(maxOutputFrames * numChannels * sizeof(int16_t), '\0');
|
||||
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
||||
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
||||
QByteArray resampledBuffer(maxOutputSize, '\0');
|
||||
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
||||
|
||||
resampler.render(reinterpret_cast<const int16_t*>(buffer.data()),
|
||||
reinterpret_cast<int16_t*>(resampledBuffer.data()),
|
||||
nInputFrames);
|
||||
resampler.render(audioData->data(), bufferPtr, numFrames);
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(resampledBuffer, options);
|
||||
int numSamples = maxOutputFrames * numChannels;
|
||||
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(newAudioData, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
||||
|
@ -481,3 +476,49 @@ AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const Au
|
|||
return injector;
|
||||
}
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options) {
|
||||
AudioInjectorPointer injector = playSound(audioData, options);
|
||||
|
||||
if (injector) {
|
||||
injector->_state |= AudioInjectorState::PendingDelete;
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const AudioInjectorOptions& options) {
|
||||
if (options.pitch == 1.0f) {
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(audioData, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
||||
}
|
||||
return injector;
|
||||
} else {
|
||||
using AudioConstants::AudioSample;
|
||||
using AudioConstants::SAMPLE_RATE;
|
||||
const int standardRate = SAMPLE_RATE;
|
||||
// limit to 4 octaves
|
||||
const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
||||
const int resampledRate = SAMPLE_RATE / pitch;
|
||||
|
||||
auto numChannels = audioData->getNumChannels();
|
||||
auto numFrames = audioData->getNumFrames();
|
||||
|
||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||
|
||||
// create a resampled buffer that is guaranteed to be large enough
|
||||
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
||||
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
||||
QByteArray resampledBuffer(maxOutputSize, '\0');
|
||||
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
||||
|
||||
resampler.render(audioData->data(), bufferPtr, numFrames);
|
||||
|
||||
int numSamples = maxOutputFrames * numChannels;
|
||||
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
||||
|
||||
return AudioInjector::playSound(newAudioData, options);
|
||||
}
|
||||
}
|
|
@ -52,8 +52,8 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs)
|
|||
class AudioInjector : public QObject, public QEnableSharedFromThis<AudioInjector> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(AudioDataPointer audioData, const AudioInjectorOptions& injectorOptions);
|
||||
~AudioInjector();
|
||||
|
||||
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
|
||||
|
@ -74,10 +74,11 @@ public:
|
|||
|
||||
bool stateHas(AudioInjectorState state) const ;
|
||||
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
||||
static AudioInjectorPointer playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options);
|
||||
static AudioInjectorPointer playSound(const QByteArray& buffer, const AudioInjectorOptions options);
|
||||
static AudioInjectorPointer playSound(SharedSoundPointer sound, const float volume,
|
||||
const float stretchFactor, const glm::vec3 position);
|
||||
|
||||
static AudioInjectorPointer playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options);
|
||||
static AudioInjectorPointer playSound(SharedSoundPointer sound, const AudioInjectorOptions& options);
|
||||
static AudioInjectorPointer playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options);
|
||||
static AudioInjectorPointer playSound(AudioDataPointer audioData, const AudioInjectorOptions& options);
|
||||
|
||||
public slots:
|
||||
void restart();
|
||||
|
@ -106,7 +107,8 @@ private:
|
|||
|
||||
static AbstractAudioInterface* _localAudioInterface;
|
||||
|
||||
QByteArray _audioData;
|
||||
const SharedSoundPointer _sound;
|
||||
AudioDataPointer _audioData;
|
||||
AudioInjectorOptions _options;
|
||||
AudioInjectorState _state { AudioInjectorState::NotFinished };
|
||||
bool _hasSentFirstFrame { false };
|
||||
|
|
|
@ -11,13 +11,9 @@
|
|||
|
||||
#include "AudioInjectorLocalBuffer.h"
|
||||
|
||||
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray) :
|
||||
_rawAudioArray(rawAudioArray),
|
||||
_shouldLoop(false),
|
||||
_isStopped(false),
|
||||
_currentOffset(0)
|
||||
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(AudioDataPointer audioData) :
|
||||
_audioData(audioData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioInjectorLocalBuffer::stop() {
|
||||
|
@ -39,7 +35,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
|||
if (!_isStopped) {
|
||||
|
||||
// first copy to the end of the raw audio
|
||||
int bytesToEnd = _rawAudioArray.size() - _currentOffset;
|
||||
int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset;
|
||||
|
||||
int bytesRead = maxSize;
|
||||
|
||||
|
@ -47,7 +43,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
|||
bytesRead = bytesToEnd;
|
||||
}
|
||||
|
||||
memcpy(data, _rawAudioArray.data() + _currentOffset, bytesRead);
|
||||
memcpy(data, _audioData->rawData() + _currentOffset, bytesRead);
|
||||
|
||||
// now check if we are supposed to loop and if we can copy more from the beginning
|
||||
if (_shouldLoop && maxSize != bytesRead) {
|
||||
|
@ -56,7 +52,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
|||
_currentOffset += bytesRead;
|
||||
}
|
||||
|
||||
if (_shouldLoop && _currentOffset == _rawAudioArray.size()) {
|
||||
if (_shouldLoop && _currentOffset == (int)_audioData->getNumBytes()) {
|
||||
_currentOffset = 0;
|
||||
}
|
||||
|
||||
|
@ -70,12 +66,12 @@ qint64 AudioInjectorLocalBuffer::recursiveReadFromFront(char* data, qint64 maxSi
|
|||
// see how much we can get in this pass
|
||||
int bytesRead = maxSize;
|
||||
|
||||
if (bytesRead > _rawAudioArray.size()) {
|
||||
bytesRead = _rawAudioArray.size();
|
||||
if (bytesRead > (int)_audioData->getNumBytes()) {
|
||||
bytesRead = _audioData->getNumBytes();
|
||||
}
|
||||
|
||||
// copy that amount
|
||||
memcpy(data, _rawAudioArray.data(), bytesRead);
|
||||
memcpy(data, _audioData->rawData(), bytesRead);
|
||||
|
||||
// check if we need to call ourselves again and pull from the front again
|
||||
if (bytesRead < maxSize) {
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
#include <glm/common.hpp>
|
||||
|
||||
#include "Sound.h"
|
||||
|
||||
class AudioInjectorLocalBuffer : public QIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray);
|
||||
AudioInjectorLocalBuffer(AudioDataPointer audioData);
|
||||
|
||||
void stop();
|
||||
|
||||
|
@ -34,11 +36,10 @@ public:
|
|||
private:
|
||||
qint64 recursiveReadFromFront(char* data, qint64 maxSize);
|
||||
|
||||
QByteArray _rawAudioArray;
|
||||
bool _shouldLoop;
|
||||
bool _isStopped;
|
||||
|
||||
int _currentOffset;
|
||||
AudioDataPointer _audioData;
|
||||
bool _shouldLoop { false };
|
||||
bool _isStopped { false };
|
||||
int _currentOffset { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_AudioInjectorLocalBuffer_h
|
||||
|
|
|
@ -33,48 +33,59 @@
|
|||
|
||||
#include "flump3dec.h"
|
||||
|
||||
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) {
|
||||
return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership);
|
||||
int audioDataPointerMetaTypeID = qRegisterMetaType<AudioDataPointer>("AudioDataPointer");
|
||||
|
||||
using AudioConstants::AudioSample;
|
||||
|
||||
AudioDataPointer AudioData::make(uint32_t numSamples, uint32_t numChannels,
|
||||
const AudioSample* samples) {
|
||||
// Compute the amount of memory required for the audio data object
|
||||
const size_t bufferSize = numSamples * sizeof(AudioSample);
|
||||
const size_t memorySize = sizeof(AudioData) + bufferSize;
|
||||
|
||||
// Allocate the memory for the audio data object and the buffer
|
||||
void* memory = ::malloc(memorySize);
|
||||
auto audioData = reinterpret_cast<AudioData*>(memory);
|
||||
auto buffer = reinterpret_cast<AudioSample*>(audioData + 1);
|
||||
assert(((char*)buffer - (char*)audioData) == sizeof(AudioData));
|
||||
|
||||
// Use placement new to construct the audio data object at the memory allocated
|
||||
::new(audioData) AudioData(numSamples, numChannels, buffer);
|
||||
|
||||
// Copy the samples to the buffer
|
||||
memcpy(buffer, samples, bufferSize);
|
||||
|
||||
// Return shared_ptr that properly destruct the object and release the memory
|
||||
return AudioDataPointer(audioData, [](AudioData* ptr) {
|
||||
ptr->~AudioData();
|
||||
::free(ptr);
|
||||
});
|
||||
}
|
||||
|
||||
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) {
|
||||
if (auto soundInterface = qobject_cast<SoundScriptingInterface*>(object.toQObject())) {
|
||||
out = soundInterface->getSound();
|
||||
}
|
||||
}
|
||||
|
||||
SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) {
|
||||
// During shutdown we can sometimes get an empty sound pointer back
|
||||
if (_sound) {
|
||||
QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
|
||||
Resource(url),
|
||||
_isStereo(isStereo),
|
||||
_isAmbisonic(isAmbisonic),
|
||||
_isReady(false)
|
||||
{
|
||||
}
|
||||
AudioData::AudioData(uint32_t numSamples, uint32_t numChannels, const AudioSample* samples)
|
||||
: _numSamples(numSamples),
|
||||
_numChannels(numChannels),
|
||||
_data(samples)
|
||||
{}
|
||||
|
||||
void Sound::downloadFinished(const QByteArray& data) {
|
||||
if (!_self) {
|
||||
soundProcessError(301, "Sound object has gone out of scope");
|
||||
return;
|
||||
}
|
||||
|
||||
// this is a QRunnable, will delete itself after it has finished running
|
||||
SoundProcessor* soundProcessor = new SoundProcessor(_url, data, _isStereo, _isAmbisonic);
|
||||
auto soundProcessor = new SoundProcessor(_self, data);
|
||||
connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess);
|
||||
connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError);
|
||||
QThreadPool::globalInstance()->start(soundProcessor);
|
||||
}
|
||||
|
||||
void Sound::soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration) {
|
||||
void Sound::soundProcessSuccess(AudioDataPointer audioData) {
|
||||
qCDebug(audio) << "Setting ready state for sound file" << _url.fileName();
|
||||
|
||||
qCDebug(audio) << "Setting ready state for sound file";
|
||||
|
||||
_byteArray = data;
|
||||
_isStereo = stereo;
|
||||
_isAmbisonic = ambisonic;
|
||||
_duration = duration;
|
||||
_isReady = true;
|
||||
_audioData = std::move(audioData);
|
||||
finishedLoading(true);
|
||||
|
||||
emit ready();
|
||||
|
@ -86,91 +97,101 @@ void Sound::soundProcessError(int error, QString str) {
|
|||
finishedLoading(false);
|
||||
}
|
||||
|
||||
|
||||
SoundProcessor::SoundProcessor(QWeakPointer<Resource> sound, QByteArray data) :
|
||||
_sound(sound),
|
||||
_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
void SoundProcessor::run() {
|
||||
auto sound = qSharedPointerCast<Sound>(_sound.lock());
|
||||
if (!sound) {
|
||||
emit onError(301, "Sound object has gone out of scope");
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(audio) << "Processing sound file";
|
||||
|
||||
// replace our byte array with the downloaded data
|
||||
QByteArray rawAudioByteArray = QByteArray(_data);
|
||||
QString fileName = _url.fileName().toLower();
|
||||
auto url = sound->getURL();
|
||||
QString fileName = url.fileName().toLower();
|
||||
qCDebug(audio) << "Processing sound file" << fileName;
|
||||
|
||||
static const QString WAV_EXTENSION = ".wav";
|
||||
static const QString MP3_EXTENSION = ".mp3";
|
||||
static const QString RAW_EXTENSION = ".raw";
|
||||
static const QString STEREO_RAW_EXTENSION = ".stereo.raw";
|
||||
QString fileType;
|
||||
|
||||
QByteArray outputAudioByteArray;
|
||||
AudioProperties properties;
|
||||
|
||||
if (fileName.endsWith(WAV_EXTENSION)) {
|
||||
|
||||
QByteArray outputAudioByteArray;
|
||||
|
||||
int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray);
|
||||
if (sampleRate == 0) {
|
||||
qCWarning(audio) << "Unsupported WAV file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unsupported WAV file type");
|
||||
return;
|
||||
}
|
||||
|
||||
downSample(outputAudioByteArray, sampleRate);
|
||||
|
||||
fileType = "WAV";
|
||||
properties = interpretAsWav(_data, outputAudioByteArray);
|
||||
} else if (fileName.endsWith(MP3_EXTENSION)) {
|
||||
|
||||
QByteArray outputAudioByteArray;
|
||||
|
||||
int sampleRate = interpretAsMP3(rawAudioByteArray, outputAudioByteArray);
|
||||
if (sampleRate == 0) {
|
||||
qCWarning(audio) << "Unsupported MP3 file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unsupported MP3 file type");
|
||||
return;
|
||||
}
|
||||
|
||||
downSample(outputAudioByteArray, sampleRate);
|
||||
|
||||
} else if (fileName.endsWith(RAW_EXTENSION)) {
|
||||
fileType = "MP3";
|
||||
properties = interpretAsMP3(_data, outputAudioByteArray);
|
||||
} else if (fileName.endsWith(STEREO_RAW_EXTENSION)) {
|
||||
// check if this was a stereo raw file
|
||||
// since it's raw the only way for us to know that is if the file was called .stereo.raw
|
||||
if (fileName.toLower().endsWith("stereo.raw")) {
|
||||
_isStereo = true;
|
||||
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes as stereo audio file.";
|
||||
}
|
||||
|
||||
qCDebug(audio) << "Processing sound of" << _data.size() << "bytes from" << fileName << "as stereo audio file.";
|
||||
// Process as 48khz RAW file
|
||||
downSample(rawAudioByteArray, 48000);
|
||||
|
||||
properties.numChannels = 2;
|
||||
properties.sampleRate = 48000;
|
||||
outputAudioByteArray = _data;
|
||||
} else if (fileName.endsWith(RAW_EXTENSION)) {
|
||||
// Process as 48khz RAW file
|
||||
properties.numChannels = 1;
|
||||
properties.sampleRate = 48000;
|
||||
outputAudioByteArray = _data;
|
||||
} else {
|
||||
qCWarning(audio) << "Unknown sound file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unknown sound file type");
|
||||
return;
|
||||
}
|
||||
|
||||
emit onSuccess(_data, _isStereo, _isAmbisonic, _duration);
|
||||
if (properties.sampleRate == 0) {
|
||||
qCWarning(audio) << "Unsupported" << fileType << "file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unsupported " + fileType + " file type");
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = downSample(outputAudioByteArray, properties);
|
||||
|
||||
int numSamples = data.size() / AudioConstants::SAMPLE_SIZE;
|
||||
auto audioData = AudioData::make(numSamples, properties.numChannels,
|
||||
(const AudioSample*)data.constData());
|
||||
emit onSuccess(audioData);
|
||||
}
|
||||
|
||||
void SoundProcessor::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
|
||||
QByteArray SoundProcessor::downSample(const QByteArray& rawAudioByteArray,
|
||||
AudioProperties properties) {
|
||||
|
||||
// we want to convert it to the format that the audio-mixer wants
|
||||
// which is signed, 16-bit, 24Khz
|
||||
|
||||
if (sampleRate == AudioConstants::SAMPLE_RATE) {
|
||||
if (properties.sampleRate == AudioConstants::SAMPLE_RATE) {
|
||||
// no resampling needed
|
||||
_data = rawAudioByteArray;
|
||||
} else {
|
||||
|
||||
int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
AudioSRC resampler(sampleRate, AudioConstants::SAMPLE_RATE, numChannels);
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_data.resize(maxDestinationBytes);
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)_data.data(),
|
||||
numSourceFrames);
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_data.resize(numDestinationBytes);
|
||||
return rawAudioByteArray;
|
||||
}
|
||||
|
||||
AudioSRC resampler(properties.sampleRate, AudioConstants::SAMPLE_RATE,
|
||||
properties.numChannels);
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (properties.numChannels * AudioConstants::SAMPLE_SIZE);
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * properties.numChannels * AudioConstants::SAMPLE_SIZE;
|
||||
QByteArray data(maxDestinationBytes, Qt::Uninitialized);
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)data.data(),
|
||||
numSourceFrames);
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * properties.numChannels * sizeof(AudioSample);
|
||||
data.resize(numDestinationBytes);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -218,7 +239,9 @@ struct WAVEFormat {
|
|||
};
|
||||
|
||||
// returns wavfile sample rate, used for resampling
|
||||
int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
SoundProcessor::AudioProperties SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray) {
|
||||
AudioProperties properties;
|
||||
|
||||
// Create a data stream to analyze the data
|
||||
QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);
|
||||
|
@ -227,7 +250,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
RIFFHeader riff;
|
||||
if (waveStream.readRawData((char*)&riff, sizeof(RIFFHeader)) != sizeof(RIFFHeader)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Parse the "RIFF" chunk
|
||||
|
@ -235,11 +258,11 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
waveStream.setByteOrder(QDataStream::LittleEndian);
|
||||
} else {
|
||||
qCWarning(audio) << "Currently not supporting big-endian audio files.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (strncmp(riff.type, "WAVE", 4) != 0) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Read chunks until the "fmt " chunk is found
|
||||
|
@ -247,7 +270,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
while (true) {
|
||||
if (waveStream.readRawData((char*)&fmt, sizeof(chunk)) != sizeof(chunk)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (strncmp(fmt.id, "fmt ", 4) == 0) {
|
||||
break;
|
||||
|
@ -259,26 +282,26 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
WAVEFormat wave;
|
||||
if (waveStream.readRawData((char*)&wave, sizeof(WAVEFormat)) != sizeof(WAVEFormat)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Parse the "fmt " chunk
|
||||
if (qFromLittleEndian<quint16>(wave.audioFormat) != WAVEFORMAT_PCM &&
|
||||
qFromLittleEndian<quint16>(wave.audioFormat) != WAVEFORMAT_EXTENSIBLE) {
|
||||
qCWarning(audio) << "Currently not supporting non PCM audio files.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (qFromLittleEndian<quint16>(wave.numChannels) == 2) {
|
||||
_isStereo = true;
|
||||
} else if (qFromLittleEndian<quint16>(wave.numChannels) == 4) {
|
||||
_isAmbisonic = true;
|
||||
} else if (qFromLittleEndian<quint16>(wave.numChannels) != 1) {
|
||||
|
||||
properties.numChannels = qFromLittleEndian<quint16>(wave.numChannels);
|
||||
if (properties.numChannels != 1 &&
|
||||
properties.numChannels != 2 &&
|
||||
properties.numChannels != 4) {
|
||||
qCWarning(audio) << "Currently not supporting audio files with other than 1/2/4 channels.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (qFromLittleEndian<quint16>(wave.bitsPerSample) != 16) {
|
||||
qCWarning(audio) << "Currently not supporting non 16bit audio files.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Skip any extra data in the "fmt " chunk
|
||||
|
@ -289,7 +312,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
while (true) {
|
||||
if (waveStream.readRawData((char*)&data, sizeof(chunk)) != sizeof(chunk)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (strncmp(data.id, "data", 4) == 0) {
|
||||
break;
|
||||
|
@ -300,17 +323,21 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
// Read the "data" chunk
|
||||
quint32 outputAudioByteArraySize = qFromLittleEndian<quint32>(data.size);
|
||||
outputAudioByteArray.resize(outputAudioByteArraySize);
|
||||
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
|
||||
auto bytesRead = waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize);
|
||||
if (bytesRead != (int)outputAudioByteArraySize) {
|
||||
qCWarning(audio) << "Error reading WAV file";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
_duration = (float)(outputAudioByteArraySize / (wave.sampleRate * wave.numChannels * wave.bitsPerSample / 8.0f));
|
||||
return wave.sampleRate;
|
||||
properties.sampleRate = wave.sampleRate;
|
||||
return properties;
|
||||
}
|
||||
|
||||
// returns MP3 sample rate, used for resampling
|
||||
int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
SoundProcessor::AudioProperties SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray) {
|
||||
AudioProperties properties;
|
||||
|
||||
using namespace flump3dec;
|
||||
|
||||
static const int MP3_SAMPLES_MAX = 1152;
|
||||
|
@ -321,21 +348,19 @@ int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteA
|
|||
// create bitstream
|
||||
Bit_stream_struc *bitstream = bs_new();
|
||||
if (bitstream == nullptr) {
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// create decoder
|
||||
mp3tl *decoder = mp3tl_new(bitstream, MP3TL_MODE_16BIT);
|
||||
if (decoder == nullptr) {
|
||||
bs_free(bitstream);
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// initialize
|
||||
bs_set_data(bitstream, (uint8_t*)inputAudioByteArray.data(), inputAudioByteArray.size());
|
||||
int frameCount = 0;
|
||||
int sampleRate = 0;
|
||||
int numChannels = 0;
|
||||
|
||||
// skip ID3 tag, if present
|
||||
Mp3TlRetcode result = mp3tl_skip_id3(decoder);
|
||||
|
@ -357,8 +382,8 @@ int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteA
|
|||
<< "channels =" << header->channels;
|
||||
|
||||
// save header info
|
||||
sampleRate = header->sample_rate;
|
||||
numChannels = header->channels;
|
||||
properties.sampleRate = header->sample_rate;
|
||||
properties.numChannels = header->channels;
|
||||
|
||||
// skip Xing header, if present
|
||||
result = mp3tl_skip_xing(decoder, header);
|
||||
|
@ -388,14 +413,32 @@ int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteA
|
|||
// free bitstream
|
||||
bs_free(bitstream);
|
||||
|
||||
int outputAudioByteArraySize = outputAudioByteArray.size();
|
||||
if (outputAudioByteArraySize == 0) {
|
||||
if (outputAudioByteArray.isEmpty()) {
|
||||
qCWarning(audio) << "Error decoding MP3 file";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
_isStereo = (numChannels == 2);
|
||||
_isAmbisonic = false;
|
||||
_duration = (float)outputAudioByteArraySize / (sampleRate * numChannels * sizeof(int16_t));
|
||||
return sampleRate;
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
||||
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) {
|
||||
return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership);
|
||||
}
|
||||
|
||||
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) {
|
||||
if (auto soundInterface = qobject_cast<SoundScriptingInterface*>(object.toQObject())) {
|
||||
out = soundInterface->getSound();
|
||||
}
|
||||
}
|
||||
|
||||
SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) {
|
||||
// During shutdown we can sometimes get an empty sound pointer back
|
||||
if (_sound) {
|
||||
QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : Resource(url) {
|
||||
_numChannels = isAmbisonic ? 4 : (isStereo ? 2 : 1);
|
||||
}
|
||||
|
|
|
@ -19,61 +19,102 @@
|
|||
|
||||
#include <ResourceCache.h>
|
||||
|
||||
#include "AudioConstants.h"
|
||||
|
||||
class AudioData;
|
||||
using AudioDataPointer = std::shared_ptr<const AudioData>;
|
||||
|
||||
Q_DECLARE_METATYPE(AudioDataPointer);
|
||||
|
||||
// AudioData is designed to be immutable
|
||||
// All of its members and methods are const
|
||||
// This makes it perfectly safe to access from multiple threads at once
|
||||
class AudioData {
|
||||
public:
|
||||
using AudioSample = AudioConstants::AudioSample;
|
||||
|
||||
// Allocates the buffer memory contiguous with the object
|
||||
static AudioDataPointer make(uint32_t numSamples, uint32_t numChannels,
|
||||
const AudioSample* samples);
|
||||
|
||||
uint32_t getNumSamples() const { return _numSamples; }
|
||||
uint32_t getNumChannels() const { return _numChannels; }
|
||||
const AudioSample* data() const { return _data; }
|
||||
const char* rawData() const { return reinterpret_cast<const char*>(_data); }
|
||||
|
||||
float isStereo() const { return _numChannels == 2; }
|
||||
float isAmbisonic() const { return _numChannels == 4; }
|
||||
float getDuration() const { return (float)_numSamples / (_numChannels * AudioConstants::SAMPLE_RATE); }
|
||||
uint32_t getNumFrames() const { return _numSamples / _numChannels; }
|
||||
uint32_t getNumBytes() const { return _numSamples * sizeof(AudioSample); }
|
||||
|
||||
private:
|
||||
AudioData(uint32_t numSamples, uint32_t numChannels, const AudioSample* samples);
|
||||
|
||||
const uint32_t _numSamples { 0 };
|
||||
const uint32_t _numChannels { 0 };
|
||||
const AudioSample* const _data { nullptr };
|
||||
};
|
||||
|
||||
class Sound : public Resource {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false);
|
||||
|
||||
bool isStereo() const { return _isStereo; }
|
||||
bool isAmbisonic() const { return _isAmbisonic; }
|
||||
bool isReady() const { return _isReady; }
|
||||
float getDuration() const { return _duration; }
|
||||
|
||||
const QByteArray& getByteArray() const { return _byteArray; }
|
||||
bool isReady() const { return (bool)_audioData; }
|
||||
|
||||
bool isStereo() const { return _audioData ? _audioData->isStereo() : false; }
|
||||
bool isAmbisonic() const { return _audioData ? _audioData->isAmbisonic() : false; }
|
||||
float getDuration() const { return _audioData ? _audioData->getDuration() : 0.0f; }
|
||||
|
||||
AudioDataPointer getAudioData() const { return _audioData; }
|
||||
|
||||
int getNumChannels() const { return _numChannels; }
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
|
||||
protected slots:
|
||||
void soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
|
||||
void soundProcessSuccess(AudioDataPointer audioData);
|
||||
void soundProcessError(int error, QString str);
|
||||
|
||||
private:
|
||||
QByteArray _byteArray;
|
||||
bool _isStereo;
|
||||
bool _isAmbisonic;
|
||||
bool _isReady;
|
||||
float _duration; // In seconds
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
AudioDataPointer _audioData;
|
||||
|
||||
// Only used for caching until the download has finished
|
||||
int _numChannels { 0 };
|
||||
};
|
||||
|
||||
class SoundProcessor : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SoundProcessor(const QUrl& url, const QByteArray& data, bool stereo, bool ambisonic)
|
||||
: _url(url), _data(data), _isStereo(stereo), _isAmbisonic(ambisonic)
|
||||
{
|
||||
}
|
||||
struct AudioProperties {
|
||||
uint8_t numChannels { 0 };
|
||||
uint32_t sampleRate { 0 };
|
||||
};
|
||||
|
||||
SoundProcessor(QWeakPointer<Resource> sound, QByteArray data);
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
|
||||
int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
int interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
QByteArray downSample(const QByteArray& rawAudioByteArray,
|
||||
AudioProperties properties);
|
||||
AudioProperties interpretAsWav(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray);
|
||||
AudioProperties interpretAsMP3(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray);
|
||||
|
||||
signals:
|
||||
void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
|
||||
void onSuccess(AudioDataPointer audioData);
|
||||
void onError(int error, QString str);
|
||||
|
||||
private:
|
||||
QUrl _url;
|
||||
QByteArray _data;
|
||||
bool _isStereo;
|
||||
bool _isAmbisonic;
|
||||
float _duration;
|
||||
const QWeakPointer<Resource> _sound;
|
||||
const QByteArray _data;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<Sound> SharedSoundPointer;
|
||||
|
|
|
@ -1031,7 +1031,14 @@ void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entit
|
|||
// Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2)
|
||||
const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f;
|
||||
const float stretchFactor = logf(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / logf(2.0f);
|
||||
AudioInjector::playSound(collisionSound, volume, stretchFactor, collision.contactPoint);
|
||||
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = collisionSound->isStereo();
|
||||
options.position = collision.contactPoint;
|
||||
options.volume = volume;
|
||||
options.pitch = 1.0f / stretchFactor;
|
||||
|
||||
AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
||||
|
|
|
@ -954,11 +954,6 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
|||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) {
|
||||
// don't delete other avatar's avatarEntities
|
||||
// If you actually own the entity but the onwership property is not set because of a domain switch
|
||||
// The lines below makes sure the entity is deleted once its properties are set.
|
||||
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
|
||||
AvatarSharedPointer myAvatar = avatarHashMap->getAvatarBySessionID(myNodeID);
|
||||
myAvatar->insertDetachedEntityID(id);
|
||||
shouldSendDeleteToServer = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "QVariantGLM.h"
|
||||
#include "EntitiesLogging.h"
|
||||
#include "RecurseOctreeToMapOperator.h"
|
||||
#include "RecurseOctreeToJSONOperator.h"
|
||||
#include "LogHandler.h"
|
||||
#include "EntityEditFilters.h"
|
||||
#include "EntityDynamicFactoryInterface.h"
|
||||
|
@ -2785,6 +2786,17 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
|||
return success;
|
||||
}
|
||||
|
||||
bool EntityTree::writeToJSON(QString& jsonString, const OctreeElementPointer& element) {
|
||||
QScriptEngine scriptEngine;
|
||||
RecurseOctreeToJSONOperator theOperator(element, &scriptEngine, jsonString);
|
||||
withReadLock([&] {
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
});
|
||||
|
||||
jsonString = theOperator.getJson();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityTree::resetClientEditStats() {
|
||||
_treeResetTime = usecTimestampNow();
|
||||
_maxEditDelta = 0;
|
||||
|
|
|
@ -224,6 +224,8 @@ public:
|
|||
virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
|
||||
bool skipThoseWithBadParents) override;
|
||||
virtual bool readFromMap(QVariantMap& entityDescription) override;
|
||||
virtual bool writeToJSON(QString& jsonString, const OctreeElementPointer& element) override;
|
||||
|
||||
|
||||
glm::vec3 getContentsDimensions();
|
||||
float getContentsLargestDimension();
|
||||
|
|
50
libraries/entities/src/RecurseOctreeToJSONOperator.cpp
Normal file
50
libraries/entities/src/RecurseOctreeToJSONOperator.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// RecurseOctreeToJSONOperator.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Simon Walton on Oct 11, 2018.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RecurseOctreeToJSONOperator.h"
|
||||
#include "EntityItemProperties.h"
|
||||
|
||||
RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine,
|
||||
QString jsonPrefix, bool skipDefaults, bool skipThoseWithBadParents):
|
||||
_engine(engine),
|
||||
_json(jsonPrefix),
|
||||
_skipDefaults(skipDefaults),
|
||||
_skipThoseWithBadParents(skipThoseWithBadParents)
|
||||
{
|
||||
_toStringMethod = _engine->evaluate("(function() { return JSON.stringify(this, null, ' ') })");
|
||||
}
|
||||
|
||||
bool RecurseOctreeToJSONOperator::postRecursion(const OctreeElementPointer& element) {
|
||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
|
||||
entityTreeElement->forEachEntity([&](const EntityItemPointer& entity) { processEntity(entity); } );
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecurseOctreeToJSONOperator::processEntity(const EntityItemPointer& entity) {
|
||||
if (_skipThoseWithBadParents && !entity->isParentIDValid()) {
|
||||
return; // we weren't able to resolve a parent from _parentID, so don't save this entity.
|
||||
}
|
||||
|
||||
QScriptValue qScriptValues = _skipDefaults
|
||||
? EntityItemNonDefaultPropertiesToScriptValue(_engine, entity->getProperties())
|
||||
: EntityItemPropertiesToScriptValue(_engine, entity->getProperties());
|
||||
|
||||
if (_comma) {
|
||||
_json += ',';
|
||||
};
|
||||
_comma = true;
|
||||
_json += "\n ";
|
||||
|
||||
// Override default toString():
|
||||
qScriptValues.setProperty("toString", _toStringMethod);
|
||||
_json += qScriptValues.toString();
|
||||
}
|
33
libraries/entities/src/RecurseOctreeToJSONOperator.h
Normal file
33
libraries/entities/src/RecurseOctreeToJSONOperator.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// RecurseOctreeToJSONOperator.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Simon Walton on Oct 11, 2018.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
||||
class RecurseOctreeToJSONOperator : public RecurseOctreeOperator {
|
||||
public:
|
||||
RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true,
|
||||
bool skipThoseWithBadParents = false);
|
||||
virtual bool preRecursion(const OctreeElementPointer& element) override { return true; };
|
||||
virtual bool postRecursion(const OctreeElementPointer& element) override;
|
||||
|
||||
QString getJson() const { return _json; }
|
||||
|
||||
private:
|
||||
void processEntity(const EntityItemPointer& entity);
|
||||
|
||||
QScriptEngine* _engine;
|
||||
QScriptValue _toStringMethod;
|
||||
|
||||
QString _json;
|
||||
const bool _skipDefaults;
|
||||
bool _skipThoseWithBadParents;
|
||||
bool _comma { false };
|
||||
};
|
|
@ -417,6 +417,30 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
|||
return filepath.mid(filepath.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
|
||||
QMap<QString, glm::quat> jointRotationOffsets;
|
||||
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset";
|
||||
if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) {
|
||||
auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash();
|
||||
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||
QString jointName = itr.key();
|
||||
QString line = itr.value().toString();
|
||||
auto quatCoords = line.split(',');
|
||||
if (quatCoords.size() == 4) {
|
||||
float quatX = quatCoords[0].mid(1).toFloat();
|
||||
float quatY = quatCoords[1].toFloat();
|
||||
float quatZ = quatCoords[2].toFloat();
|
||||
float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat();
|
||||
if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) {
|
||||
glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ);
|
||||
jointRotationOffsets.insert(jointName, rotationOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return jointRotationOffsets;
|
||||
}
|
||||
|
||||
HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QString& url) {
|
||||
const FBXNode& node = _rootNode;
|
||||
QMap<QString, ExtractedMesh> meshes;
|
||||
|
@ -1793,6 +1817,19 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -329,6 +329,8 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
|
|||
result = GL_RGBA8_SNORM;
|
||||
break;
|
||||
case gpu::NINT2_10_10_10:
|
||||
result = GL_RGB10_A2;
|
||||
break;
|
||||
case gpu::NUINT32:
|
||||
case gpu::NINT32:
|
||||
case gpu::COMPRESSED:
|
||||
|
@ -729,9 +731,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.internalFormat = GL_DEPTH_COMPONENT24;
|
||||
break;
|
||||
}
|
||||
case gpu::NINT2_10_10_10:
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUINT2:
|
||||
case gpu::NINT2_10_10_10:
|
||||
case gpu::NUM_TYPES: { // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
@ -893,9 +895,12 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.format = GL_RGBA;
|
||||
texel.internalFormat = GL_RGBA2;
|
||||
break;
|
||||
case gpu::NINT2_10_10_10:
|
||||
texel.format = GL_RGBA;
|
||||
texel.internalFormat = GL_RGB10_A2;
|
||||
break;
|
||||
case gpu::NUINT32:
|
||||
case gpu::NINT32:
|
||||
case gpu::NINT2_10_10_10:
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUM_TYPES: // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
|
|
|
@ -49,26 +49,7 @@ void GL41Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
|||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
uint32 startVertex = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
if (isStereo()) {
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
|
||||
#else
|
||||
setupStereoSide(0);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
setupStereoSide(1);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
#endif
|
||||
_stats._DSNumTriangles += 2 * numVertices / 3;
|
||||
_stats._DSNumDrawcalls += 2;
|
||||
|
||||
} else {
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
_stats._DSNumTriangles += numVertices / 3;
|
||||
_stats._DSNumDrawcalls++;
|
||||
}
|
||||
_stats._DSNumAPIDrawcalls++;
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
draw(mode, numVertices, startVertex);
|
||||
}
|
||||
|
||||
void GL41Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
|
||||
|
|
|
@ -72,27 +72,7 @@ void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
|||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
uint32 startVertex = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
if (isStereo()) {
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
|
||||
#else
|
||||
setupStereoSide(0);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
setupStereoSide(1);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
#endif
|
||||
|
||||
_stats._DSNumTriangles += 2 * numVertices / 3;
|
||||
_stats._DSNumDrawcalls += 2;
|
||||
|
||||
} else {
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
_stats._DSNumTriangles += numVertices / 3;
|
||||
_stats._DSNumDrawcalls++;
|
||||
}
|
||||
_stats._DSNumAPIDrawcalls++;
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
draw(mode, numVertices, startVertex);
|
||||
}
|
||||
|
||||
void GL45Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
|
||||
|
|
|
@ -49,28 +49,7 @@ void GLESBackend::do_draw(const Batch& batch, size_t paramOffset) {
|
|||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
uint32 startVertex = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
if (isStereo()) {
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
|
||||
#else
|
||||
|
||||
setupStereoSide(0);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
setupStereoSide(1);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
|
||||
#endif
|
||||
_stats._DSNumTriangles += 2 * numVertices / 3;
|
||||
_stats._DSNumDrawcalls += 2;
|
||||
|
||||
} else {
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
_stats._DSNumTriangles += numVertices / 3;
|
||||
_stats._DSNumDrawcalls++;
|
||||
}
|
||||
_stats._DSNumAPIDrawcalls++;
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
draw(mode, numVertices, startVertex);
|
||||
}
|
||||
|
||||
void GLESBackend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include "ShaderConstants.h"
|
||||
|
||||
#include "GPULogging.h"
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "Frame.h"
|
||||
#include "GPULogging.h"
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
using namespace gpu;
|
||||
|
||||
|
@ -331,11 +332,20 @@ Size Context::getTextureResourcePopulatedGPUMemSize() {
|
|||
return Backend::textureResourcePopulatedGPUMemSize.getValue();
|
||||
}
|
||||
|
||||
PipelinePointer Context::createMipGenerationPipeline(const ShaderPointer& ps) {
|
||||
auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawViewportQuadTransformTexcoord);
|
||||
static gpu::StatePointer state(new gpu::State());
|
||||
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
return gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
||||
Size Context::getTextureResourceIdealGPUMemSize() {
|
||||
return Backend::textureResourceIdealGPUMemSize.getValue();
|
||||
}
|
||||
|
||||
|
||||
BatchPointer Context::acquireBatch(const char* name) {
|
||||
Batch* rawBatch = nullptr;
|
||||
{
|
||||
|
|
|
@ -218,6 +218,8 @@ public:
|
|||
// Same as above but grabbed at every end of a frame
|
||||
void getFrameStats(ContextStats& stats) const;
|
||||
|
||||
static PipelinePointer createMipGenerationPipeline(const ShaderPointer& pixelShader);
|
||||
|
||||
double getFrameTimerGPUAverage() const;
|
||||
double getFrameTimerBatchAverage() const;
|
||||
|
||||
|
|
|
@ -311,6 +311,8 @@ public:
|
|||
QString getModelNameOfMesh(int meshIndex) const;
|
||||
|
||||
QList<QString> blendshapeChannelNames;
|
||||
|
||||
QMap<int, glm::quat> jointRotationOffsets;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -245,6 +245,13 @@ void GeometryReader::run() {
|
|||
QMetaObject::invokeMethod(resource.data(), "finishedLoading",
|
||||
Q_ARG(bool, false));
|
||||
}
|
||||
} catch (QString& e) {
|
||||
qCWarning(modelnetworking) << "Exception while loading model --" << e;
|
||||
auto resource = _resource.toStrongRef();
|
||||
if (resource) {
|
||||
QMetaObject::invokeMethod(resource.data(), "finishedLoading",
|
||||
Q_ARG(bool, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -234,7 +234,7 @@ private:
|
|||
#ifdef Q_OS_ANDROID
|
||||
Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
|
||||
#else
|
||||
Setting::Handle<bool> _enableInterstitialMode { "enableInterstitialMode", true };
|
||||
Setting::Handle<bool> _enableInterstitialMode { "enableInterstitialMode", false };
|
||||
#endif
|
||||
|
||||
QSet<QString> _domainConnectionRefusals;
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
#include "OctreeLogging.h"
|
||||
#include "OctreeQueryNode.h"
|
||||
#include "OctreeUtils.h"
|
||||
|
||||
#include "OctreeEntitiesFileParser.h"
|
||||
|
||||
QVector<QString> PERSIST_EXTENSIONS = {"json", "json.gz"};
|
||||
|
||||
|
@ -792,28 +792,26 @@ bool Octree::readFromStream(
|
|||
}
|
||||
|
||||
|
||||
namespace {
|
||||
// hack to get the marketplace id into the entities. We will create a way to get this from a hash of
|
||||
// the entity later, but this helps us move things along for now
|
||||
QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QString& marketplaceID) {
|
||||
QVariantMap addMarketplaceIDToDocumentEntities(QVariantMap& doc, const QString& marketplaceID) {
|
||||
if (!marketplaceID.isEmpty()) {
|
||||
QJsonDocument newDoc;
|
||||
QJsonObject rootObj = doc.object();
|
||||
QJsonArray newEntitiesArray;
|
||||
QVariantList newEntitiesArray;
|
||||
|
||||
// build a new entities array
|
||||
auto entitiesArray = rootObj["Entities"].toArray();
|
||||
for(auto it = entitiesArray.begin(); it != entitiesArray.end(); it++) {
|
||||
auto entity = (*it).toObject();
|
||||
auto entitiesArray = doc["Entities"].toList();
|
||||
for (auto it = entitiesArray.begin(); it != entitiesArray.end(); it++) {
|
||||
auto entity = (*it).toMap();
|
||||
entity["marketplaceID"] = marketplaceID;
|
||||
newEntitiesArray.append(entity);
|
||||
}
|
||||
rootObj["Entities"] = newEntitiesArray;
|
||||
newDoc.setObject(rootObj);
|
||||
return newDoc;
|
||||
doc["Entities"] = newEntitiesArray;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
} // Unnamed namepsace
|
||||
const int READ_JSON_BUFFER_SIZE = 2048;
|
||||
|
||||
bool Octree::readJSONFromStream(
|
||||
|
@ -839,12 +837,18 @@ bool Octree::readJSONFromStream(
|
|||
jsonBuffer += QByteArray(rawData, got);
|
||||
}
|
||||
|
||||
QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer);
|
||||
if (!marketplaceID.isEmpty()) {
|
||||
asDocument = addMarketplaceIDToDocumentEntities(asDocument, marketplaceID);
|
||||
OctreeEntitiesFileParser octreeParser;
|
||||
octreeParser.setEntitiesString(jsonBuffer);
|
||||
QVariantMap asMap;
|
||||
if (!octreeParser.parseEntities(asMap)) {
|
||||
qCritical() << "Couldn't parse Entities JSON:" << octreeParser.getErrorString().c_str();
|
||||
return false;
|
||||
}
|
||||
QVariant asVariant = asDocument.toVariant();
|
||||
QVariantMap asMap = asVariant.toMap();
|
||||
|
||||
if (!marketplaceID.isEmpty()) {
|
||||
addMarketplaceIDToDocumentEntities(asMap, marketplaceID);
|
||||
}
|
||||
|
||||
bool success = readFromMap(asMap);
|
||||
delete[] rawData;
|
||||
return success;
|
||||
|
@ -889,26 +893,52 @@ bool Octree::toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& elem
|
|||
return false;
|
||||
}
|
||||
|
||||
*doc = QJsonDocument::fromVariant(entityDescription);
|
||||
|
||||
bool noEntities = entityDescription["Entities"].toList().empty();
|
||||
QJsonDocument jsonDocTree = QJsonDocument::fromVariant(entityDescription);
|
||||
QJsonValue entitiesJson = jsonDocTree["Entities"];
|
||||
if (entitiesJson.isNull() || (entitiesJson.toArray().empty() && !noEntities)) {
|
||||
// Json version of entities too large.
|
||||
return false;
|
||||
} else {
|
||||
*doc = jsonDocTree;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Octree::toJSONString(QString& jsonString, const OctreeElementPointer& element) {
|
||||
OctreeElementPointer top;
|
||||
if (element) {
|
||||
top = element;
|
||||
} else {
|
||||
top = _rootElement;
|
||||
}
|
||||
|
||||
jsonString += QString("{\n \"DataVersion\": %1,\n \"Entities\": [").arg(_persistDataVersion);
|
||||
|
||||
writeToJSON(jsonString, top);
|
||||
|
||||
// include the "bitstream" version
|
||||
PacketType expectedType = expectedDataPacketType();
|
||||
PacketVersion expectedVersion = versionForPacketType(expectedType);
|
||||
|
||||
jsonString += QString("\n ],\n \"Id\": \"%1\",\n \"Version\": %2\n}\n").arg(_persistID.toString()).arg((int)expectedVersion);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool doGzip) {
|
||||
QJsonDocument doc;
|
||||
if (!toJSONDocument(&doc, element)) {
|
||||
qCritical("Failed to convert Entities to QVariantMap while converting to json.");
|
||||
return false;
|
||||
}
|
||||
QString jsonString;
|
||||
toJSONString(jsonString);
|
||||
|
||||
if (doGzip) {
|
||||
QByteArray jsonData = doc.toJson();
|
||||
|
||||
if (!gzip(jsonData, *data, -1)) {
|
||||
if (!gzip(jsonString.toUtf8(), *data, -1)) {
|
||||
qCritical("Unable to gzip data while saving to json.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
*data = doc.toJson();
|
||||
*data = jsonString.toUtf8();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -202,11 +202,13 @@ public:
|
|||
|
||||
// Octree exporters
|
||||
bool toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& element = nullptr);
|
||||
bool toJSONString(QString& jsonString, const OctreeElementPointer& element = nullptr);
|
||||
bool toJSON(QByteArray* data, const OctreeElementPointer& element = nullptr, bool doGzip = false);
|
||||
bool writeToFile(const char* filename, const OctreeElementPointer& element = nullptr, QString persistAsFileType = "json.gz");
|
||||
bool writeToJSONFile(const char* filename, const OctreeElementPointer& element = nullptr, bool doGzip = false);
|
||||
virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
|
||||
bool skipThoseWithBadParents) = 0;
|
||||
virtual bool writeToJSON(QString& jsonString, const OctreeElementPointer& element) = 0;
|
||||
|
||||
// Octree importers
|
||||
bool readFromFile(const char* filename);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
#include "OctreeDataUtils.h"
|
||||
#include "OctreeEntitiesFileParser.h"
|
||||
|
||||
#include <Gzip.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
@ -18,33 +19,13 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QFile>
|
||||
|
||||
// Reads octree file and parses it into a QJsonDocument. Handles both gzipped and non-gzipped files.
|
||||
// Returns true if the file was successfully opened and parsed, otherwise false.
|
||||
// Example failures: file does not exist, gzipped file cannot be unzipped, invalid JSON.
|
||||
bool readOctreeFile(QString path, QJsonDocument* doc) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(const QVariantMap& map) {
|
||||
if (map.contains("Id") && map.contains("DataVersion") && map.contains("Version")) {
|
||||
id = map["Id"].toUuid();
|
||||
dataVersion = map["DataVersion"].toInt();
|
||||
version = map["Version"].toInt();
|
||||
}
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
QByteArray jsonData;
|
||||
|
||||
if (!gunzip(data, jsonData)) {
|
||||
jsonData = data;
|
||||
}
|
||||
|
||||
*doc = QJsonDocument::fromJson(jsonData);
|
||||
return !doc->isNull();
|
||||
}
|
||||
|
||||
bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) {
|
||||
if (root.contains("Id") && root.contains("DataVersion") && root.contains("Version")) {
|
||||
id = root["Id"].toVariant().toUuid();
|
||||
dataVersion = root["DataVersion"].toInt();
|
||||
version = root["Version"].toInt();
|
||||
}
|
||||
readSubclassData(root);
|
||||
readSubclassData(map);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -54,40 +35,41 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromData(QByteArray data) {
|
|||
data = jsonData;
|
||||
}
|
||||
|
||||
auto doc = QJsonDocument::fromJson(data);
|
||||
if (doc.isNull()) {
|
||||
OctreeEntitiesFileParser jsonParser;
|
||||
jsonParser.setEntitiesString(data);
|
||||
QVariantMap entitiesMap;
|
||||
if (!jsonParser.parseEntities(entitiesMap)) {
|
||||
qCritical() << "Can't parse Entities JSON: " << jsonParser.getErrorString().c_str();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
return readOctreeDataInfoFromJSON(root);
|
||||
return readOctreeDataInfoFromMap(entitiesMap);
|
||||
}
|
||||
|
||||
// Reads octree file and parses it into a RawOctreeData object.
|
||||
// Returns false if readOctreeFile fails.
|
||||
bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) {
|
||||
QJsonDocument doc;
|
||||
if (!readOctreeFile(path, &doc)) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Cannot open json file for reading: " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
return readOctreeDataInfoFromJSON(root);
|
||||
QByteArray data = file.readAll();
|
||||
|
||||
return readOctreeDataInfoFromData(data);
|
||||
}
|
||||
|
||||
QByteArray OctreeUtils::RawOctreeData::toByteArray() {
|
||||
QJsonObject obj {
|
||||
{ "DataVersion", QJsonValue((qint64)dataVersion) },
|
||||
{ "Id", QJsonValue(id.toString()) },
|
||||
{ "Version", QJsonValue((qint64)version) },
|
||||
};
|
||||
QByteArray jsonString;
|
||||
|
||||
writeSubclassData(obj);
|
||||
jsonString += QString("{\n \"DataVersion\": %1,\n").arg(dataVersion);
|
||||
|
||||
QJsonDocument doc;
|
||||
doc.setObject(obj);
|
||||
writeSubclassData(jsonString);
|
||||
|
||||
return doc.toJson();
|
||||
jsonString += QString(",\n \"Id\": \"%1\",\n \"Version\": %2\n}").arg(id.toString()).arg(version);
|
||||
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
QByteArray OctreeUtils::RawOctreeData::toGzippedByteArray() {
|
||||
|
@ -114,14 +96,21 @@ void OctreeUtils::RawOctreeData::resetIdAndVersion() {
|
|||
qDebug() << "Reset octree data to: " << id << dataVersion;
|
||||
}
|
||||
|
||||
void OctreeUtils::RawEntityData::readSubclassData(const QJsonObject& root) {
|
||||
if (root.contains("Entities")) {
|
||||
entityData = root["Entities"].toArray();
|
||||
void OctreeUtils::RawEntityData::readSubclassData(const QVariantMap& root) {
|
||||
variantEntityData = root["Entities"].toList();
|
||||
}
|
||||
|
||||
void OctreeUtils::RawEntityData::writeSubclassData(QByteArray& root) const {
|
||||
root += " \"Entities\": [";
|
||||
for (auto entityIter = variantEntityData.begin(); entityIter != variantEntityData.end(); ++entityIter) {
|
||||
if (entityIter != variantEntityData.begin()) {
|
||||
root += ",";
|
||||
}
|
||||
root += "\n ";
|
||||
// Convert to string and remove trailing LF.
|
||||
root += QJsonDocument(entityIter->toJsonObject()).toJson().chopped(1);
|
||||
}
|
||||
root += "]";
|
||||
}
|
||||
|
||||
void OctreeUtils::RawEntityData::writeSubclassData(QJsonObject& root) const {
|
||||
root["Entities"] = entityData;
|
||||
}
|
||||
|
||||
PacketType OctreeUtils::RawEntityData::dataPacketType() const { return PacketType::EntityData; }
|
||||
PacketType OctreeUtils::RawEntityData::dataPacketType() const { return PacketType::EntityData; }
|
||||
|
|
|
@ -33,8 +33,8 @@ public:
|
|||
|
||||
virtual PacketType dataPacketType() const;
|
||||
|
||||
virtual void readSubclassData(const QJsonObject& root) { }
|
||||
virtual void writeSubclassData(QJsonObject& root) const { }
|
||||
virtual void readSubclassData(const QVariantMap& root) { }
|
||||
virtual void writeSubclassData(QByteArray& root) const { }
|
||||
|
||||
void resetIdAndVersion();
|
||||
QByteArray toByteArray();
|
||||
|
@ -42,15 +42,16 @@ public:
|
|||
|
||||
bool readOctreeDataInfoFromData(QByteArray data);
|
||||
bool readOctreeDataInfoFromFile(QString path);
|
||||
bool readOctreeDataInfoFromJSON(QJsonObject root);
|
||||
bool readOctreeDataInfoFromMap(const QVariantMap& map);
|
||||
};
|
||||
|
||||
class RawEntityData : public RawOctreeData {
|
||||
public:
|
||||
PacketType dataPacketType() const override;
|
||||
void readSubclassData(const QJsonObject& root) override;
|
||||
void writeSubclassData(QJsonObject& root) const override;
|
||||
void readSubclassData(const QVariantMap& root) override;
|
||||
void writeSubclassData(QByteArray& root) const override;
|
||||
|
||||
QJsonArray entityData;
|
||||
QVariantList variantEntityData;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
276
libraries/octree/src/OctreeEntitiesFileParser.cpp
Normal file
276
libraries/octree/src/OctreeEntitiesFileParser.cpp
Normal file
|
@ -0,0 +1,276 @@
|
|||
//
|
||||
// OctreeEntititesFileParser.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Simon Walton on Oct 15, 2018.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <sstream>
|
||||
#include <cctype>
|
||||
#include <QUuid>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "OctreeEntitiesFileParser.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
std::string OctreeEntitiesFileParser::getErrorString() const {
|
||||
std::ostringstream err;
|
||||
if (_errorString.size() != 0) {
|
||||
err << "Error: Line " << _line << ", byte position " << _position << ": " << _errorString;
|
||||
};
|
||||
|
||||
return err.str();
|
||||
}
|
||||
|
||||
void OctreeEntitiesFileParser::setEntitiesString(const QByteArray& entitiesContents) {
|
||||
_entitiesContents = entitiesContents;
|
||||
_entitiesLength = _entitiesContents.length();
|
||||
_position = 0;
|
||||
_line = 1;
|
||||
}
|
||||
|
||||
bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
|
||||
if (nextToken() != '{') {
|
||||
_errorString = "Text before start of object";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool gotDataVersion = false;
|
||||
bool gotEntities = false;
|
||||
bool gotId = false;
|
||||
bool gotVersion = false;
|
||||
|
||||
int token = nextToken();
|
||||
|
||||
while (true) {
|
||||
if (token == '}') {
|
||||
break;
|
||||
}
|
||||
else if (token != '"') {
|
||||
_errorString = "Incorrect key string";
|
||||
return false;
|
||||
}
|
||||
|
||||
string key = readString();
|
||||
if (key.size() == 0) {
|
||||
_errorString = "Missing object key";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextToken() != ':') {
|
||||
_errorString = "Ill-formed id/value entry";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == "DataVersion") {
|
||||
if (gotDataVersion) {
|
||||
_errorString = "Duplicate DataVersion entries";
|
||||
return false;
|
||||
}
|
||||
|
||||
int dataVersionValue = readInteger();
|
||||
parsedEntities["DataVersion"] = dataVersionValue;
|
||||
gotDataVersion = true;
|
||||
} else if (key == "Entities") {
|
||||
if (gotEntities) {
|
||||
_errorString = "Duplicate Entities entries";
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantList entitiesValue;
|
||||
if (!readEntitiesArray(entitiesValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parsedEntities["Entities"] = std::move(entitiesValue);
|
||||
gotEntities = true;
|
||||
} else if (key == "Id") {
|
||||
if (gotId) {
|
||||
_errorString = "Duplicate Id entries";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextToken() != '"') {
|
||||
_errorString = "Invalid Id value";
|
||||
return false;
|
||||
};
|
||||
string idString = readString();
|
||||
if (idString.size() == 0) {
|
||||
_errorString = "Invalid Id string";
|
||||
return false;
|
||||
}
|
||||
QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) );
|
||||
if (idValue.isNull()) {
|
||||
_errorString = "Id value invalid UUID string: " + idString;
|
||||
return false;
|
||||
}
|
||||
|
||||
parsedEntities["Id"] = idValue;
|
||||
gotId = true;
|
||||
} else if (key == "Version") {
|
||||
if (gotVersion) {
|
||||
_errorString = "Duplicate Version entries";
|
||||
return false;
|
||||
}
|
||||
|
||||
int versionValue = readInteger();
|
||||
parsedEntities["Version"] = versionValue;
|
||||
gotVersion = true;
|
||||
} else if (key == "Paths") {
|
||||
// Serverless JSON has optional Paths entry.
|
||||
if (nextToken() != '{') {
|
||||
_errorString = "Paths item is not an object";
|
||||
return false;
|
||||
}
|
||||
|
||||
int matchingBrace = findMatchingBrace();
|
||||
if (matchingBrace < 0) {
|
||||
_errorString = "Unterminated entity object";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray jsonObject = _entitiesContents.mid(_position - 1, matchingBrace - _position + 1);
|
||||
QJsonDocument pathsObject = QJsonDocument::fromJson(jsonObject);
|
||||
if (pathsObject.isNull()) {
|
||||
_errorString = "Ill-formed paths entry";
|
||||
return false;
|
||||
}
|
||||
|
||||
parsedEntities["Paths"] = pathsObject.object();
|
||||
_position = matchingBrace;
|
||||
} else {
|
||||
_errorString = "Unrecognized key name: " + key;
|
||||
return false;
|
||||
}
|
||||
|
||||
token = nextToken();
|
||||
if (token == ',') {
|
||||
token = nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
if (nextToken() != -1) {
|
||||
_errorString = "Ill-formed end of object";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int OctreeEntitiesFileParser::nextToken() {
|
||||
while (_position < _entitiesLength) {
|
||||
char c = _entitiesContents[_position++];
|
||||
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
|
||||
return c;
|
||||
}
|
||||
if (c == '\n') {
|
||||
++_line;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
string OctreeEntitiesFileParser::readString() {
|
||||
string returnString;
|
||||
while (_position < _entitiesLength) {
|
||||
char c = _entitiesContents[_position++];
|
||||
if (c == '"') {
|
||||
break;
|
||||
} else {
|
||||
returnString.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
return returnString;
|
||||
}
|
||||
|
||||
int OctreeEntitiesFileParser::readInteger() {
|
||||
const char* currentPosition = _entitiesContents.constData() + _position;
|
||||
int i = std::atoi(currentPosition);
|
||||
|
||||
int token;
|
||||
do {
|
||||
token = nextToken();
|
||||
} while (token == '-' || token == '+' || std::isdigit(token));
|
||||
|
||||
--_position;
|
||||
return i;
|
||||
}
|
||||
|
||||
bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) {
|
||||
if (nextToken() != '[') {
|
||||
_errorString = "Entities entry is not an array";
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (nextToken() != '{') {
|
||||
_errorString = "Entity array item is not an object";
|
||||
return false;
|
||||
}
|
||||
int matchingBrace = findMatchingBrace();
|
||||
if (matchingBrace < 0) {
|
||||
_errorString = "Unterminated entity object";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray jsonEntity = _entitiesContents.mid(_position - 1, matchingBrace - _position + 1);
|
||||
QJsonDocument entity = QJsonDocument::fromJson(jsonEntity);
|
||||
if (entity.isNull()) {
|
||||
_errorString = "Ill-formed entity";
|
||||
return false;
|
||||
}
|
||||
|
||||
entitiesArray.append(entity.object());
|
||||
_position = matchingBrace;
|
||||
char c = nextToken();
|
||||
if (c == ']') {
|
||||
return true;
|
||||
} else if (c != ',') {
|
||||
_errorString = "Entity array item incorrectly terminated";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int OctreeEntitiesFileParser::findMatchingBrace() const {
|
||||
int index = _position;
|
||||
int nestCount = 1;
|
||||
while (index < _entitiesLength && nestCount != 0) {
|
||||
switch (_entitiesContents[index++]) {
|
||||
case '{':
|
||||
++nestCount;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
--nestCount;
|
||||
break;
|
||||
|
||||
case '"':
|
||||
// Skip string
|
||||
while (index < _entitiesLength) {
|
||||
if (_entitiesContents[index] == '"') {
|
||||
++index;
|
||||
break;
|
||||
} else if (_entitiesContents[index] == '\\' && _entitiesContents[++index] == 'u') {
|
||||
index += 4;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nestCount == 0 ? index : -1;
|
||||
}
|
40
libraries/octree/src/OctreeEntitiesFileParser.h
Normal file
40
libraries/octree/src/OctreeEntitiesFileParser.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// OctreeEntititesFileParser.h
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Simon Walton on Oct 15, 2018.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// Parse the top-level of the Models object ourselves - use QJsonDocument for each Entity object.
|
||||
|
||||
#ifndef hifi_OctreeEntitiesFileParser_h
|
||||
#define hifi_OctreeEntitiesFileParser_h
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
|
||||
class OctreeEntitiesFileParser {
|
||||
public:
|
||||
void setEntitiesString(const QByteArray& entitiesContents);
|
||||
bool parseEntities(QVariantMap& parsedEntities);
|
||||
std::string getErrorString() const;
|
||||
|
||||
private:
|
||||
int nextToken();
|
||||
std::string readString();
|
||||
int readInteger();
|
||||
bool readEntitiesArray(QVariantList& entitiesArray);
|
||||
int findMatchingBrace() const;
|
||||
|
||||
QByteArray _entitiesContents;
|
||||
int _position { 0 };
|
||||
int _line { 1 };
|
||||
int _entitiesLength { 0 };
|
||||
std::string _errorString;
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeEntitiesFileParser_h
|
|
@ -31,6 +31,7 @@
|
|||
#include <NumericalConstants.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PathUtils.h>
|
||||
#include <Gzip.h>
|
||||
|
||||
#include "OctreeLogging.h"
|
||||
#include "OctreeUtils.h"
|
||||
|
@ -72,14 +73,27 @@ void OctreePersistThread::start() {
|
|||
|
||||
OctreeUtils::RawOctreeData data;
|
||||
qCDebug(octree) << "Reading octree data from" << _filename;
|
||||
if (data.readOctreeDataInfoFromFile(_filename)) {
|
||||
qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")";
|
||||
packet->writePrimitive(true);
|
||||
auto id = data.id.toRfc4122();
|
||||
packet->write(id);
|
||||
packet->writePrimitive(data.version);
|
||||
QFile file(_filename);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QByteArray jsonData(file.readAll());
|
||||
file.close();
|
||||
if (!gunzip(jsonData, _cachedJSONData)) {
|
||||
_cachedJSONData = jsonData;
|
||||
}
|
||||
|
||||
if (data.readOctreeDataInfoFromData(_cachedJSONData)) {
|
||||
qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")";
|
||||
packet->writePrimitive(true);
|
||||
auto id = data.id.toRfc4122();
|
||||
packet->write(id);
|
||||
packet->writePrimitive(data.version);
|
||||
} else {
|
||||
_cachedJSONData.clear();
|
||||
qCWarning(octree) << "No octree data found";
|
||||
packet->writePrimitive(false);
|
||||
}
|
||||
} else {
|
||||
qCWarning(octree) << "No octree data found";
|
||||
qCWarning(octree) << "Couldn't access file" << _filename << file.errorString();
|
||||
packet->writePrimitive(false);
|
||||
}
|
||||
|
||||
|
@ -99,6 +113,7 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer<ReceivedMessa
|
|||
OctreeUtils::RawOctreeData data;
|
||||
bool hasValidOctreeData { false };
|
||||
if (includesNewData) {
|
||||
_cachedJSONData.clear();
|
||||
replacementData = message->readAll();
|
||||
replaceData(replacementData);
|
||||
hasValidOctreeData = data.readOctreeDataInfoFromFile(_filename);
|
||||
|
@ -108,7 +123,7 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer<ReceivedMessa
|
|||
|
||||
OctreeUtils::RawEntityData data;
|
||||
qCDebug(octree) << "Reading octree data from" << _filename;
|
||||
if (data.readOctreeDataInfoFromFile(_filename)) {
|
||||
if (data.readOctreeDataInfoFromData(_cachedJSONData)) {
|
||||
hasValidOctreeData = true;
|
||||
if (data.id.isNull()) {
|
||||
qCDebug(octree) << "Current octree data has a null id, updating";
|
||||
|
@ -138,10 +153,16 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer<ReceivedMessa
|
|||
_tree->withWriteLock([&] {
|
||||
PerformanceWarning warn(true, "Loading Octree File", true);
|
||||
|
||||
persistentFileRead = _tree->readFromFile(_filename.toLocal8Bit().constData());
|
||||
if (_cachedJSONData.isEmpty()) {
|
||||
persistentFileRead = _tree->readFromFile(_filename.toLocal8Bit().constData());
|
||||
} else {
|
||||
QDataStream jsonStream(_cachedJSONData);
|
||||
persistentFileRead = _tree->readFromStream(-1, jsonStream);
|
||||
}
|
||||
_tree->pruneTree();
|
||||
});
|
||||
|
||||
_cachedJSONData.clear();
|
||||
quint64 loadDone = usecTimestampNow();
|
||||
_loadTimeUSecs = loadDone - loadStarted;
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ private:
|
|||
quint64 _lastTimeDebug;
|
||||
|
||||
QString _persistAsFileType;
|
||||
QByteArray _cachedJSONData;
|
||||
};
|
||||
|
||||
#endif // hifi_OctreePersistThread_h
|
||||
|
|
|
@ -65,6 +65,6 @@ bool PluginUtils::isOculusTouchControllerAvailable() {
|
|||
};
|
||||
|
||||
bool PluginUtils::isXboxControllerAvailable() {
|
||||
return isSubdeviceContainingNameAvailable("X360 Controller");
|
||||
return isSubdeviceContainingNameAvailable("X360 Controller") || isSubdeviceContainingNameAvailable("XInput Controller");
|
||||
};
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer {
|
|||
vec4 date;
|
||||
// Offset 16, acts as vec4 for alignment purposes
|
||||
vec3 worldPosition;
|
||||
// Offset 32, acts as vec4 for alignment purposes
|
||||
// Offset 32, acts as vec4 for alignment purposes (but not packing purposes)
|
||||
vec3 worldScale;
|
||||
// We need this float here to keep globalTime from getting pulled to offset 44
|
||||
float _spare0;
|
||||
// Offset 48
|
||||
float globalTime;
|
||||
// Offset 52
|
||||
|
|
|
@ -387,12 +387,8 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
|
|||
if (!parent) {
|
||||
parent = getRootItem();
|
||||
}
|
||||
// manually control children items lifetime
|
||||
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::CppOwnership);
|
||||
|
||||
// add object to the manual deletion list
|
||||
_sharedObject->addToDeletionList(newObject);
|
||||
|
||||
// Allow child windows to be destroyed from JS
|
||||
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
|
||||
newObject->setParent(parent);
|
||||
newItem->setParentItem(parent);
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <QtQml/QQmlEngine>
|
||||
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QPointer>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <shared/NsightHelpers.h>
|
||||
|
@ -82,6 +81,7 @@ SharedObject::SharedObject() {
|
|||
SharedObject::~SharedObject() {
|
||||
// After destroy returns, the rendering thread should be gone
|
||||
destroy();
|
||||
|
||||
// _renderTimer is created with `this` as the parent, so need no explicit destruction
|
||||
#ifndef DISABLE_QML
|
||||
// Destroy the event hand
|
||||
|
@ -96,11 +96,6 @@ SharedObject::~SharedObject() {
|
|||
}
|
||||
#endif
|
||||
|
||||
// already deleted objects will be reset to null by QPointer so it should be safe just iterate here
|
||||
for (auto qmlObject : _deletionList) {
|
||||
delete qmlObject;
|
||||
}
|
||||
|
||||
if (_rootItem) {
|
||||
delete _rootItem;
|
||||
_rootItem = nullptr;
|
||||
|
@ -417,11 +412,6 @@ bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void hifi::qml::impl::SharedObject::addToDeletionList(QObject * object)
|
||||
{
|
||||
_deletionList.append(QPointer<QObject>(object));
|
||||
}
|
||||
|
||||
void SharedObject::setProxyWindow(QWindow* window) {
|
||||
#ifndef DISABLE_QML
|
||||
_proxyWindow = window;
|
||||
|
|
|
@ -66,7 +66,7 @@ public:
|
|||
void resume();
|
||||
bool isPaused() const;
|
||||
bool fetchTexture(TextureAndFence& textureAndFence);
|
||||
void addToDeletionList(QObject* object);
|
||||
|
||||
|
||||
private:
|
||||
bool event(QEvent* e) override;
|
||||
|
@ -91,8 +91,6 @@ private:
|
|||
void onAboutToQuit();
|
||||
void updateTextureAndFence(const TextureAndFence& newTextureAndFence);
|
||||
|
||||
QList<QPointer<QObject>> _deletionList;
|
||||
|
||||
// Texture management
|
||||
TextureAndFence _latestTextureAndFence{ 0, 0 };
|
||||
QQuickItem* _item{ nullptr };
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,6 +21,8 @@
|
|||
#include "DeferredFramebuffer.h"
|
||||
#include "SurfaceGeometryPass.h"
|
||||
|
||||
#include "ssao_shared.h"
|
||||
|
||||
class AmbientOcclusionFramebuffer {
|
||||
public:
|
||||
AmbientOcclusionFramebuffer();
|
||||
|
@ -30,13 +32,23 @@ public:
|
|||
|
||||
gpu::FramebufferPointer getOcclusionBlurredFramebuffer();
|
||||
gpu::TexturePointer getOcclusionBlurredTexture();
|
||||
|
||||
|
||||
gpu::FramebufferPointer getNormalFramebuffer();
|
||||
gpu::TexturePointer getNormalTexture();
|
||||
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
gpu::FramebufferPointer getOcclusionSplitFramebuffer(int index);
|
||||
gpu::TexturePointer getOcclusionSplitTexture();
|
||||
#endif
|
||||
|
||||
// Update the source framebuffer size which will drive the allocation of all the other resources.
|
||||
void updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer);
|
||||
bool update(const gpu::TexturePointer& linearDepthBuffer, int resolutionLevel, int depthResolutionLevel, bool isStereo);
|
||||
gpu::TexturePointer getLinearDepthTexture();
|
||||
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
|
||||
|
||||
bool isStereo() const { return _isStereo; }
|
||||
|
||||
protected:
|
||||
|
||||
void clear();
|
||||
void allocate();
|
||||
|
||||
|
@ -44,12 +56,22 @@ protected:
|
|||
|
||||
gpu::FramebufferPointer _occlusionFramebuffer;
|
||||
gpu::TexturePointer _occlusionTexture;
|
||||
|
||||
|
||||
gpu::FramebufferPointer _occlusionBlurredFramebuffer;
|
||||
gpu::TexturePointer _occlusionBlurredTexture;
|
||||
|
||||
|
||||
|
||||
gpu::FramebufferPointer _normalFramebuffer;
|
||||
gpu::TexturePointer _normalTexture;
|
||||
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
gpu::FramebufferPointer _occlusionSplitFramebuffers[SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT];
|
||||
gpu::TexturePointer _occlusionSplitTexture;
|
||||
#endif
|
||||
|
||||
glm::ivec2 _frameSize;
|
||||
int _resolutionLevel{ 0 };
|
||||
int _depthResolutionLevel{ 0 };
|
||||
bool _isStereo{ false };
|
||||
};
|
||||
|
||||
using AmbientOcclusionFramebufferPointer = std::shared_ptr<AmbientOcclusionFramebuffer>;
|
||||
|
@ -57,53 +79,78 @@ using AmbientOcclusionFramebufferPointer = std::shared_ptr<AmbientOcclusionFrame
|
|||
class AmbientOcclusionEffectConfig : public render::GPUJobConfig::Persistent {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty)
|
||||
Q_PROPERTY(bool horizonBased MEMBER horizonBased NOTIFY dirty)
|
||||
Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled NOTIFY dirty)
|
||||
Q_PROPERTY(bool borderingEnabled MEMBER borderingEnabled NOTIFY dirty)
|
||||
Q_PROPERTY(bool fetchMipsEnabled MEMBER fetchMipsEnabled NOTIFY dirty)
|
||||
Q_PROPERTY(float radius MEMBER radius WRITE setRadius)
|
||||
Q_PROPERTY(float obscuranceLevel MEMBER obscuranceLevel WRITE setObscuranceLevel)
|
||||
Q_PROPERTY(float falloffBias MEMBER falloffBias WRITE setFalloffBias)
|
||||
Q_PROPERTY(float edgeSharpness MEMBER edgeSharpness WRITE setEdgeSharpness)
|
||||
Q_PROPERTY(float blurDeviation MEMBER blurDeviation WRITE setBlurDeviation)
|
||||
Q_PROPERTY(float numSpiralTurns MEMBER numSpiralTurns WRITE setNumSpiralTurns)
|
||||
Q_PROPERTY(int numSamples MEMBER numSamples WRITE setNumSamples)
|
||||
Q_PROPERTY(bool jitterEnabled MEMBER jitterEnabled NOTIFY dirty)
|
||||
|
||||
Q_PROPERTY(int resolutionLevel MEMBER resolutionLevel WRITE setResolutionLevel)
|
||||
Q_PROPERTY(float edgeSharpness MEMBER edgeSharpness WRITE setEdgeSharpness)
|
||||
Q_PROPERTY(int blurRadius MEMBER blurRadius WRITE setBlurRadius)
|
||||
|
||||
// SSAO
|
||||
Q_PROPERTY(float ssaoRadius MEMBER ssaoRadius WRITE setSSAORadius)
|
||||
Q_PROPERTY(float ssaoObscuranceLevel MEMBER ssaoObscuranceLevel WRITE setSSAOObscuranceLevel)
|
||||
Q_PROPERTY(float ssaoFalloffAngle MEMBER ssaoFalloffAngle WRITE setSSAOFalloffAngle)
|
||||
Q_PROPERTY(float ssaoNumSpiralTurns MEMBER ssaoNumSpiralTurns WRITE setSSAONumSpiralTurns)
|
||||
Q_PROPERTY(int ssaoNumSamples MEMBER ssaoNumSamples WRITE setSSAONumSamples)
|
||||
|
||||
// HBAO
|
||||
Q_PROPERTY(float hbaoRadius MEMBER hbaoRadius WRITE setHBAORadius)
|
||||
Q_PROPERTY(float hbaoObscuranceLevel MEMBER hbaoObscuranceLevel WRITE setHBAOObscuranceLevel)
|
||||
Q_PROPERTY(float hbaoFalloffAngle MEMBER hbaoFalloffAngle WRITE setHBAOFalloffAngle)
|
||||
Q_PROPERTY(int hbaoNumSamples MEMBER hbaoNumSamples WRITE setHBAONumSamples)
|
||||
|
||||
public:
|
||||
AmbientOcclusionEffectConfig() : render::GPUJobConfig::Persistent(QStringList() << "Render" << "Engine" << "Ambient Occlusion", false) {}
|
||||
AmbientOcclusionEffectConfig();
|
||||
|
||||
const int MAX_RESOLUTION_LEVEL = 4;
|
||||
const int MAX_BLUR_RADIUS = 6;
|
||||
const int MAX_BLUR_RADIUS = 15;
|
||||
|
||||
void setRadius(float newRadius) { radius = std::max(0.01f, newRadius); emit dirty(); }
|
||||
void setObscuranceLevel(float level) { obscuranceLevel = std::max(0.01f, level); emit dirty(); }
|
||||
void setFalloffBias(float bias) { falloffBias = std::max(0.0f, std::min(bias, 0.2f)); emit dirty(); }
|
||||
void setEdgeSharpness(float sharpness) { edgeSharpness = std::max(0.0f, (float)sharpness); emit dirty(); }
|
||||
void setBlurDeviation(float deviation) { blurDeviation = std::max(0.0f, deviation); emit dirty(); }
|
||||
void setNumSpiralTurns(float turns) { numSpiralTurns = std::max(0.0f, (float)turns); emit dirty(); }
|
||||
void setNumSamples(int samples) { numSamples = std::max(1.0f, (float)samples); emit dirty(); }
|
||||
void setResolutionLevel(int level) { resolutionLevel = std::max(0, std::min(level, MAX_RESOLUTION_LEVEL)); emit dirty(); }
|
||||
void setBlurRadius(int radius) { blurRadius = std::max(0, std::min(MAX_BLUR_RADIUS, radius)); emit dirty(); }
|
||||
void setEdgeSharpness(float sharpness);
|
||||
void setResolutionLevel(int level);
|
||||
void setBlurRadius(int radius);
|
||||
|
||||
float radius{ 0.5f };
|
||||
float perspectiveScale{ 1.0f };
|
||||
float obscuranceLevel{ 0.5f }; // intensify or dim down the obscurance effect
|
||||
float falloffBias{ 0.01f };
|
||||
float edgeSharpness{ 1.0f };
|
||||
float blurDeviation{ 2.5f };
|
||||
float numSpiralTurns{ 7.0f }; // defining an angle span to distribute the samples ray directions
|
||||
int numSamples{ 16 };
|
||||
int resolutionLevel{ 1 };
|
||||
int blurRadius{ 4 }; // 0 means no blurring
|
||||
bool ditheringEnabled{ true }; // randomize the distribution of taps per pixel, should always be true
|
||||
bool borderingEnabled{ true }; // avoid evaluating information from non existing pixels out of the frame, should always be true
|
||||
bool fetchMipsEnabled{ true }; // fetch taps in sub mips to otpimize cache, should always be true
|
||||
void setSSAORadius(float newRadius);
|
||||
void setSSAOObscuranceLevel(float level);
|
||||
void setSSAOFalloffAngle(float bias);
|
||||
void setSSAONumSpiralTurns(float turns);
|
||||
void setSSAONumSamples(int samples);
|
||||
|
||||
void setHBAORadius(float newRadius);
|
||||
void setHBAOObscuranceLevel(float level);
|
||||
void setHBAOFalloffAngle(float bias);
|
||||
void setHBAONumSamples(int samples);
|
||||
|
||||
float perspectiveScale;
|
||||
float edgeSharpness;
|
||||
int blurRadius; // 0 means no blurring
|
||||
int resolutionLevel;
|
||||
|
||||
float ssaoRadius;
|
||||
float ssaoObscuranceLevel; // intensify or dim down the obscurance effect
|
||||
float ssaoFalloffAngle;
|
||||
float ssaoNumSpiralTurns; // defining an angle span to distribute the samples ray directions
|
||||
int ssaoNumSamples;
|
||||
|
||||
float hbaoRadius;
|
||||
float hbaoObscuranceLevel; // intensify or dim down the obscurance effect
|
||||
float hbaoFalloffAngle;
|
||||
int hbaoNumSamples;
|
||||
|
||||
bool horizonBased; // Use horizon based AO
|
||||
bool ditheringEnabled; // randomize the distribution of taps per pixel, should always be true
|
||||
bool borderingEnabled; // avoid evaluating information from non existing pixels out of the frame, should always be true
|
||||
bool fetchMipsEnabled; // fetch taps in sub mips to otpimize cache, should always be true
|
||||
bool jitterEnabled; // Add small jittering to AO samples at each frame
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
||||
#define SSAO_RANDOM_SAMPLE_COUNT 16
|
||||
|
||||
class AmbientOcclusionEffect {
|
||||
public:
|
||||
using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer>;
|
||||
|
@ -115,59 +162,75 @@ public:
|
|||
|
||||
void configure(const Config& config);
|
||||
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
|
||||
|
||||
|
||||
// Class describing the uniform buffer with all the parameters common to the AO shaders
|
||||
class Parameters {
|
||||
class AOParameters : public AmbientOcclusionParams {
|
||||
public:
|
||||
// Resolution info
|
||||
glm::vec4 resolutionInfo { -1.0f, 0.0f, 1.0f, 0.0f };
|
||||
// radius info is { R, R^2, 1 / R^6, ObscuranceScale}
|
||||
glm::vec4 radiusInfo{ 0.5f, 0.5f * 0.5f, 1.0f / (0.25f * 0.25f * 0.25f), 1.0f };
|
||||
// Dithering info
|
||||
glm::vec4 ditheringInfo { 0.0f, 0.0f, 0.01f, 1.0f };
|
||||
// Sampling info
|
||||
glm::vec4 sampleInfo { 11.0f, 1.0f/11.0f, 7.0f, 1.0f };
|
||||
// Blurring info
|
||||
glm::vec4 blurInfo { 1.0f, 3.0f, 2.0f, 0.0f };
|
||||
// gaussian distribution coefficients first is the sampling radius (max is 6)
|
||||
const static int GAUSSIAN_COEFS_LENGTH = 8;
|
||||
float _gaussianCoefs[GAUSSIAN_COEFS_LENGTH];
|
||||
|
||||
Parameters() {}
|
||||
|
||||
int getResolutionLevel() const { return resolutionInfo.x; }
|
||||
float getRadius() const { return radiusInfo.x; }
|
||||
float getPerspectiveScale() const { return resolutionInfo.z; }
|
||||
float getObscuranceLevel() const { return radiusInfo.w; }
|
||||
float getFalloffBias() const { return (float)ditheringInfo.z; }
|
||||
float getEdgeSharpness() const { return (float)blurInfo.x; }
|
||||
float getBlurDeviation() const { return blurInfo.z; }
|
||||
AOParameters();
|
||||
|
||||
int getResolutionLevel() const { return _resolutionInfo.x; }
|
||||
float getRadius() const { return _radiusInfo.x; }
|
||||
float getPerspectiveScale() const { return _resolutionInfo.z; }
|
||||
float getObscuranceLevel() const { return _radiusInfo.w; }
|
||||
float getFalloffAngle() const { return (float)_falloffInfo.x; }
|
||||
|
||||
float getNumSpiralTurns() const { return sampleInfo.z; }
|
||||
int getNumSamples() const { return (int)sampleInfo.x; }
|
||||
bool isFetchMipsEnabled() const { return sampleInfo.w; }
|
||||
float getNumSpiralTurns() const { return _sampleInfo.z; }
|
||||
int getNumSamples() const { return (int)_sampleInfo.x; }
|
||||
bool isFetchMipsEnabled() const { return _sampleInfo.w; }
|
||||
|
||||
bool isDitheringEnabled() const { return _ditheringInfo.x != 0.0f; }
|
||||
bool isBorderingEnabled() const { return _ditheringInfo.w != 0.0f; }
|
||||
bool isHorizonBased() const { return _resolutionInfo.y != 0.0f; }
|
||||
|
||||
int getBlurRadius() const { return (int)blurInfo.y; }
|
||||
bool isDitheringEnabled() const { return ditheringInfo.x; }
|
||||
bool isBorderingEnabled() const { return ditheringInfo.w; }
|
||||
};
|
||||
using ParametersBuffer = gpu::StructBuffer<Parameters>;
|
||||
using AOParametersBuffer = gpu::StructBuffer<AOParameters>;
|
||||
|
||||
private:
|
||||
void updateGaussianDistribution();
|
||||
|
||||
ParametersBuffer _parametersBuffer;
|
||||
|
||||
const gpu::PipelinePointer& getOcclusionPipeline();
|
||||
const gpu::PipelinePointer& getHBlurPipeline(); // first
|
||||
const gpu::PipelinePointer& getVBlurPipeline(); // second
|
||||
// Class describing the uniform buffer with all the parameters common to the bilateral blur shaders
|
||||
class BlurParameters : public AmbientOcclusionBlurParams {
|
||||
public:
|
||||
|
||||
gpu::PipelinePointer _occlusionPipeline;
|
||||
gpu::PipelinePointer _hBlurPipeline;
|
||||
gpu::PipelinePointer _vBlurPipeline;
|
||||
BlurParameters();
|
||||
|
||||
float getEdgeSharpness() const { return (float)_blurInfo.x; }
|
||||
int getBlurRadius() const { return (int)_blurInfo.w; }
|
||||
|
||||
};
|
||||
using BlurParametersBuffer = gpu::StructBuffer<BlurParameters>;
|
||||
|
||||
using FrameParametersBuffer = gpu::StructBuffer< AmbientOcclusionFrameParams>;
|
||||
|
||||
void updateBlurParameters();
|
||||
void updateFramebufferSizes();
|
||||
void updateRandomSamples();
|
||||
void updateJitterSamples();
|
||||
|
||||
int getDepthResolutionLevel() const;
|
||||
|
||||
AOParametersBuffer _aoParametersBuffer;
|
||||
FrameParametersBuffer _aoFrameParametersBuffer[SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT];
|
||||
BlurParametersBuffer _vblurParametersBuffer;
|
||||
BlurParametersBuffer _hblurParametersBuffer;
|
||||
float _blurEdgeSharpness{ 0.0f };
|
||||
|
||||
static const gpu::PipelinePointer& getOcclusionPipeline();
|
||||
static const gpu::PipelinePointer& getBilateralBlurPipeline();
|
||||
static const gpu::PipelinePointer& getMipCreationPipeline();
|
||||
static const gpu::PipelinePointer& getGatherPipeline();
|
||||
static const gpu::PipelinePointer& getBuildNormalsPipeline();
|
||||
|
||||
static gpu::PipelinePointer _occlusionPipeline;
|
||||
static gpu::PipelinePointer _bilateralBlurPipeline;
|
||||
static gpu::PipelinePointer _mipCreationPipeline;
|
||||
static gpu::PipelinePointer _gatherPipeline;
|
||||
static gpu::PipelinePointer _buildNormalsPipeline;
|
||||
|
||||
AmbientOcclusionFramebufferPointer _framebuffer;
|
||||
std::array<float, SSAO_RANDOM_SAMPLE_COUNT * SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT> _randomSamples;
|
||||
int _frameId{ 0 };
|
||||
bool _isJitterEnabled{ true };
|
||||
|
||||
gpu::RangeTimerPointer _gpuTimer;
|
||||
|
||||
|
@ -193,7 +256,7 @@ signals:
|
|||
|
||||
class DebugAmbientOcclusion {
|
||||
public:
|
||||
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer, AmbientOcclusionEffect::ParametersBuffer>;
|
||||
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer, AmbientOcclusionEffect::AOParametersBuffer>;
|
||||
using Config = DebugAmbientOcclusionConfig;
|
||||
using JobModel = render::Job::ModelI<DebugAmbientOcclusion, Inputs, Config>;
|
||||
|
||||
|
|
|
@ -114,18 +114,22 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
for (int i = 0; i < (int)_meshStates.size(); i++) {
|
||||
Model::MeshState& state = _meshStates[i];
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
int meshIndex = i;
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
int clusterIndex = j;
|
||||
|
||||
if (_useDualQuaternionSkinning) {
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans());
|
||||
} else {
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,9 +150,11 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
|
||||
Model::MeshState& state = _cauterizeMeshStates[i];
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
int meshIndex = i;
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
int clusterIndex = j;
|
||||
|
||||
if (_useDualQuaternionSkinning) {
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||
|
@ -157,7 +163,7 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
} else {
|
||||
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans());
|
||||
}
|
||||
|
@ -166,7 +172,7 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
// not cauterized so just copy the value from the non-cauterized version.
|
||||
state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j];
|
||||
} else {
|
||||
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
glm_mat4u_mul(cauterizeMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,9 +220,7 @@ static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{
|
|||
|
||||
static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{
|
||||
"vec4 getFragmentColor() {"
|
||||
" return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);"
|
||||
// When drawing color " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);"
|
||||
// when drawing normal" return vec4(normalize(texture(debugTexture0, uv).xyz * 2.0 - vec3(1.0)), 1.0);"
|
||||
" return vec4(vec3(texture(debugTexture0, uv).x), 1.0);"
|
||||
" }"
|
||||
};
|
||||
static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{
|
||||
|
@ -323,6 +321,8 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, const std::strin
|
|||
return DEFAULT_AMBIENT_OCCLUSION_SHADER;
|
||||
case AmbientOcclusionBlurredMode:
|
||||
return DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER;
|
||||
case AmbientOcclusionNormalMode:
|
||||
return DEFAULT_HALF_NORMAL_SHADER;
|
||||
case VelocityMode:
|
||||
return DEFAULT_VELOCITY_SHADER;
|
||||
case CustomMode:
|
||||
|
@ -470,6 +470,8 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
|
|||
batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionTexture());
|
||||
} else if (_mode == AmbientOcclusionBlurredMode) {
|
||||
batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionBlurredTexture());
|
||||
} else if (_mode == AmbientOcclusionNormalMode) {
|
||||
batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getNormalTexture());
|
||||
}
|
||||
}
|
||||
const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
|
|
@ -91,6 +91,7 @@ protected:
|
|||
ScatteringDebugMode,
|
||||
AmbientOcclusionMode,
|
||||
AmbientOcclusionBlurredMode,
|
||||
AmbientOcclusionNormalMode,
|
||||
VelocityMode,
|
||||
CustomMode, // Needs to stay last
|
||||
|
||||
|
|
|
@ -120,13 +120,22 @@ float getStereoSideHeight(int resolutionLevel) {
|
|||
return float(int(frameTransform._pixelInfo.w) >> resolutionLevel);
|
||||
}
|
||||
|
||||
vec2 getSideImageSize(int resolutionLevel) {
|
||||
return vec2(float(int(frameTransform._stereoInfo.y) >> resolutionLevel), float(int(frameTransform._pixelInfo.w) >> resolutionLevel));
|
||||
vec2 getStereoSideSize(int resolutionLevel) {
|
||||
return vec2(getStereoSideWidth(resolutionLevel), getStereoSideHeight(resolutionLevel));
|
||||
}
|
||||
|
||||
ivec4 getStereoSideInfoFromWidth(int xPos, int sideWidth) {
|
||||
return ivec4(xPos < sideWidth ? ivec2(0, 0) : ivec2(1, sideWidth), sideWidth, isStereo());
|
||||
}
|
||||
|
||||
ivec4 getStereoSideInfo(int xPos, int resolutionLevel) {
|
||||
int sideWidth = int(getStereoSideWidth(resolutionLevel));
|
||||
return ivec4(xPos < sideWidth ? ivec2(0, 0) : ivec2(1, sideWidth), sideWidth, isStereo());
|
||||
return getStereoSideInfoFromWidth(xPos, sideWidth);
|
||||
}
|
||||
|
||||
|
||||
int getStereoSide(ivec4 sideInfo) {
|
||||
return sideInfo.x;
|
||||
}
|
||||
|
||||
float evalZeyeFromZdb(float depth) {
|
||||
|
|
|
@ -1418,18 +1418,21 @@ void Model::updateClusterMatrices() {
|
|||
const HFMModel& hfmModel = getHFMModel();
|
||||
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
||||
MeshState& state = _meshStates[i];
|
||||
int meshIndex = i;
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
int clusterIndex = j;
|
||||
|
||||
if (_useDualQuaternionSkinning) {
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
} else {
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,32 +46,25 @@ void SoftAttachmentModel::updateClusterMatrices() {
|
|||
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
||||
MeshState& state = _meshStates[i];
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
|
||||
int meshIndex = i;
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||
|
||||
int clusterIndex = j;
|
||||
// TODO: cache these look-ups as an optimization
|
||||
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
if (_useDualQuaternionSkinning) {
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
|
||||
glm::mat4 m;
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m);
|
||||
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, m);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m);
|
||||
} else {
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "SurfaceGeometryPass.h"
|
||||
|
||||
#include <limits>
|
||||
#include <MathUtils.h>
|
||||
|
||||
#include <gpu/Context.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
@ -28,19 +29,27 @@ namespace ru {
|
|||
LinearDepthFramebuffer::LinearDepthFramebuffer() {
|
||||
}
|
||||
|
||||
void LinearDepthFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) {
|
||||
void LinearDepthFramebuffer::update(const gpu::TexturePointer& depthBuffer, const gpu::TexturePointer& normalTexture, bool isStereo) {
|
||||
//If the depth buffer or size changed, we need to delete our FBOs
|
||||
bool reset = false;
|
||||
if ((_primaryDepthTexture != depthBuffer)) {
|
||||
if (_primaryDepthTexture != depthBuffer || _normalTexture != normalTexture) {
|
||||
_primaryDepthTexture = depthBuffer;
|
||||
_normalTexture = normalTexture;
|
||||
reset = true;
|
||||
}
|
||||
if (_primaryDepthTexture) {
|
||||
auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions());
|
||||
if (_frameSize != newFrameSize) {
|
||||
if (_frameSize != newFrameSize || _isStereo != isStereo) {
|
||||
_frameSize = newFrameSize;
|
||||
_halfFrameSize = newFrameSize >> 1;
|
||||
|
||||
_halfFrameSize = _frameSize;
|
||||
if (isStereo) {
|
||||
_halfFrameSize.x >>= 1;
|
||||
}
|
||||
_halfFrameSize >>= 1;
|
||||
if (isStereo) {
|
||||
_halfFrameSize.x <<= 1;
|
||||
}
|
||||
_isStereo = isStereo;
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
|
@ -64,16 +73,22 @@ void LinearDepthFramebuffer::allocate() {
|
|||
auto height = _frameSize.y;
|
||||
|
||||
// For Linear Depth:
|
||||
_linearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), width, height, gpu::Texture::SINGLE_MIP,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
|
||||
const uint16_t LINEAR_DEPTH_MAX_MIP_LEVEL = 5;
|
||||
// Point sampling of the depth, as well as the clamp to edge, are needed for the AmbientOcclusionEffect with HBAO
|
||||
const auto depthSamplerFull = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_POINT, gpu::Sampler::WRAP_CLAMP);
|
||||
_linearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), width, height, LINEAR_DEPTH_MAX_MIP_LEVEL,
|
||||
depthSamplerFull);
|
||||
_linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("linearDepth"));
|
||||
_linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture);
|
||||
_linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat());
|
||||
|
||||
// For Downsampling:
|
||||
const uint16_t HALF_LINEAR_DEPTH_MAX_MIP_LEVEL = 5;
|
||||
_halfLinearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), _halfFrameSize.x, _halfFrameSize.y, HALF_LINEAR_DEPTH_MAX_MIP_LEVEL,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
|
||||
// Point sampling of the depth, as well as the clamp to edge, are needed for the AmbientOcclusionEffect with HBAO
|
||||
const auto depthSamplerHalf = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_POINT, gpu::Sampler::WRAP_CLAMP);
|
||||
// The depth format here is half float as it increases performance in the AmbientOcclusion. But it might be needed elsewhere...
|
||||
_halfLinearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RED), _halfFrameSize.x, _halfFrameSize.y, HALF_LINEAR_DEPTH_MAX_MIP_LEVEL,
|
||||
depthSamplerHalf);
|
||||
|
||||
_halfNormalTexture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _halfFrameSize.x, _halfFrameSize.y, gpu::Texture::SINGLE_MIP,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
|
||||
|
@ -97,6 +112,10 @@ gpu::TexturePointer LinearDepthFramebuffer::getLinearDepthTexture() {
|
|||
return _linearDepthTexture;
|
||||
}
|
||||
|
||||
gpu::TexturePointer LinearDepthFramebuffer::getNormalTexture() {
|
||||
return _normalTexture;
|
||||
}
|
||||
|
||||
gpu::FramebufferPointer LinearDepthFramebuffer::getDownsampleFramebuffer() {
|
||||
if (!_downsampleFramebuffer) {
|
||||
allocate();
|
||||
|
@ -141,11 +160,12 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con
|
|||
if (!_linearDepthFramebuffer) {
|
||||
_linearDepthFramebuffer = std::make_shared<LinearDepthFramebuffer>();
|
||||
}
|
||||
_linearDepthFramebuffer->updatePrimaryDepth(deferredFramebuffer->getPrimaryDepthTexture());
|
||||
|
||||
auto depthBuffer = deferredFramebuffer->getPrimaryDepthTexture();
|
||||
auto normalTexture = deferredFramebuffer->getDeferredNormalTexture();
|
||||
|
||||
_linearDepthFramebuffer->update(depthBuffer, normalTexture, args->isStereo());
|
||||
|
||||
auto linearDepthFBO = _linearDepthFramebuffer->getLinearDepthFramebuffer();
|
||||
auto linearDepthTexture = _linearDepthFramebuffer->getLinearDepthTexture();
|
||||
|
||||
|
@ -167,32 +187,34 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con
|
|||
float clearLinearDepth = args->getViewFrustum().getFarClip() * 2.0f;
|
||||
|
||||
gpu::doInBatch("LinearDepthPass::run", args->_context, [=](gpu::Batch& batch) {
|
||||
PROFILE_RANGE_BATCH(batch, "LinearDepthPass");
|
||||
_gpuTimer->begin(batch);
|
||||
|
||||
batch.enableStereo(false);
|
||||
|
||||
batch.setViewportTransform(depthViewport);
|
||||
batch.setProjectionTransform(glm::mat4());
|
||||
batch.resetViewTransform();
|
||||
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize(), depthViewport));
|
||||
|
||||
batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer());
|
||||
|
||||
// LinearDepth
|
||||
batch.setViewportTransform(depthViewport);
|
||||
batch.setFramebuffer(linearDepthFBO);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(clearLinearDepth, 0.0f, 0.0f, 0.0f));
|
||||
batch.setPipeline(linearDepthPipeline);
|
||||
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize(), depthViewport));
|
||||
batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, depthBuffer);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
|
||||
// Downsample
|
||||
batch.setViewportTransform(halfViewport);
|
||||
|
||||
batch.setFramebuffer(downsampleFBO);
|
||||
batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, linearDepthTexture);
|
||||
batch.setResourceTexture(ru::Texture::SurfaceGeometryNormal, normalTexture);
|
||||
batch.setPipeline(downsamplePipeline);
|
||||
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize() >> 1, halfViewport));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
|
||||
|
||||
_gpuTimer->end(batch);
|
||||
});
|
||||
|
||||
|
@ -244,7 +266,7 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline(const render:
|
|||
SurfaceGeometryFramebuffer::SurfaceGeometryFramebuffer() {
|
||||
}
|
||||
|
||||
void SurfaceGeometryFramebuffer::updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer) {
|
||||
void SurfaceGeometryFramebuffer::update(const gpu::TexturePointer& linearDepthBuffer) {
|
||||
//If the depth buffer or size changed, we need to delete our FBOs
|
||||
bool reset = false;
|
||||
if ((_linearDepthTexture != linearDepthBuffer)) {
|
||||
|
@ -411,7 +433,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext,
|
|||
if (!_surfaceGeometryFramebuffer) {
|
||||
_surfaceGeometryFramebuffer = std::make_shared<SurfaceGeometryFramebuffer>();
|
||||
}
|
||||
_surfaceGeometryFramebuffer->updateLinearDepth(linearDepthTexture);
|
||||
_surfaceGeometryFramebuffer->update(linearDepthTexture);
|
||||
|
||||
auto curvatureFramebuffer = _surfaceGeometryFramebuffer->getCurvatureFramebuffer();
|
||||
auto curvatureTexture = _surfaceGeometryFramebuffer->getCurvatureTexture();
|
||||
|
|
|
@ -28,17 +28,17 @@ public:
|
|||
|
||||
gpu::FramebufferPointer getLinearDepthFramebuffer();
|
||||
gpu::TexturePointer getLinearDepthTexture();
|
||||
gpu::TexturePointer getNormalTexture();
|
||||
|
||||
gpu::FramebufferPointer getDownsampleFramebuffer();
|
||||
gpu::TexturePointer getHalfLinearDepthTexture();
|
||||
gpu::TexturePointer getHalfNormalTexture();
|
||||
|
||||
// Update the depth buffer which will drive the allocation of all the other resources according to its size.
|
||||
void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer);
|
||||
gpu::TexturePointer getPrimaryDepthTexture();
|
||||
void update(const gpu::TexturePointer& depthBuffer, const gpu::TexturePointer& normalTexture, bool isStereo);
|
||||
const glm::ivec2& getDepthFrameSize() const { return _frameSize; }
|
||||
|
||||
void setResolutionLevel(int level);
|
||||
void setResolutionLevel(int level) { _resolutionLevel = std::max(0, level); }
|
||||
int getResolutionLevel() const { return _resolutionLevel; }
|
||||
|
||||
protected:
|
||||
|
@ -49,6 +49,7 @@ protected:
|
|||
|
||||
gpu::FramebufferPointer _linearDepthFramebuffer;
|
||||
gpu::TexturePointer _linearDepthTexture;
|
||||
gpu::TexturePointer _normalTexture;
|
||||
|
||||
gpu::FramebufferPointer _downsampleFramebuffer;
|
||||
gpu::TexturePointer _halfLinearDepthTexture;
|
||||
|
@ -58,6 +59,7 @@ protected:
|
|||
glm::ivec2 _frameSize;
|
||||
glm::ivec2 _halfFrameSize;
|
||||
int _resolutionLevel{ 0 };
|
||||
bool _isStereo{ false };
|
||||
};
|
||||
|
||||
using LinearDepthFramebufferPointer = std::shared_ptr<LinearDepthFramebuffer>;
|
||||
|
@ -107,7 +109,7 @@ public:
|
|||
gpu::TexturePointer getBlurringTexture();
|
||||
|
||||
// Update the source framebuffer size which will drive the allocation of all the other resources.
|
||||
void updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer);
|
||||
void update(const gpu::TexturePointer& linearDepthBuffer);
|
||||
gpu::TexturePointer getLinearDepthTexture();
|
||||
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
|
||||
|
||||
|
|
|
@ -86,7 +86,10 @@
|
|||
// Ambient occlusion
|
||||
#define RENDER_UTILS_BUFFER_SSAO_PARAMS 2
|
||||
#define RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS 3
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_PYRAMID 1
|
||||
#define RENDER_UTILS_BUFFER_SSAO_BLUR_PARAMS 4
|
||||
#define RENDER_UTILS_BUFFER_SSAO_FRAME_PARAMS 5
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_DEPTH 1
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_NORMAL 2
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_OCCLUSION 0
|
||||
|
||||
// Temporal anti-aliasing
|
||||
|
@ -120,7 +123,6 @@
|
|||
#define RENDER_UTILS_UNIFORM_TEXT_COLOR 0
|
||||
#define RENDER_UTILS_UNIFORM_TEXT_OUTLINE 1
|
||||
|
||||
|
||||
// Debugging
|
||||
#define RENDER_UTILS_BUFFER_DEBUG_SKYBOX 5
|
||||
#define RENDER_UTILS_DEBUG_TEXTURE0 11
|
||||
|
@ -144,7 +146,9 @@ enum Buffer {
|
|||
LightClusterContent = RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT,
|
||||
SsscParams = RENDER_UTILS_BUFFER_SSSC_PARAMS,
|
||||
SsaoParams = RENDER_UTILS_BUFFER_SSAO_PARAMS,
|
||||
SsaoFrameParams = RENDER_UTILS_BUFFER_SSAO_FRAME_PARAMS,
|
||||
SsaoDebugParams = RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS,
|
||||
SsaoBlurParams = RENDER_UTILS_BUFFER_SSAO_BLUR_PARAMS,
|
||||
LightIndex = RENDER_UTILS_BUFFER_LIGHT_INDEX,
|
||||
TaaParams = RENDER_UTILS_BUFFER_TAA_PARAMS,
|
||||
HighlightParams = RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS,
|
||||
|
@ -183,7 +187,8 @@ enum Texture {
|
|||
TaaDepth = RENDER_UTILS_TEXTURE_TAA_DEPTH,
|
||||
TaaNext = RENDER_UTILS_TEXTURE_TAA_NEXT,
|
||||
SsaoOcclusion = RENDER_UTILS_TEXTURE_SSAO_OCCLUSION,
|
||||
SsaoPyramid = RENDER_UTILS_TEXTURE_SSAO_PYRAMID,
|
||||
SsaoDepth = RENDER_UTILS_TEXTURE_SSAO_DEPTH,
|
||||
SsaoNormal = RENDER_UTILS_TEXTURE_SSAO_NORMAL,
|
||||
HighlightSceneDepth = RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH,
|
||||
HighlightDepth = RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH,
|
||||
SurfaceGeometryDepth = RENDER_UTILS_TEXTURE_SG_DEPTH,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue