mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-16 14:41:36 +02:00
Merge branch 'game-render-interface' of https://github.com/highfidelity/hifi into blue
This commit is contained in:
commit
328dd9c108
79 changed files with 1954 additions and 1451 deletions
|
@ -980,7 +980,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
connect(scriptEngines, &ScriptEngines::scriptLoadError,
|
||||
scriptEngines, [](const QString& filename, const QString& error){
|
||||
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
|
||||
OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load.");
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -1845,7 +1845,7 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas
|
|||
case DomainHandler::ConnectionRefusedReason::Unknown: {
|
||||
QString message = "Unable to connect to the location you are visiting.\n";
|
||||
message += reasonMessage;
|
||||
OffscreenUi::warning("", message);
|
||||
OffscreenUi::asyncWarning("", message);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -2714,7 +2714,6 @@ void Application::paintGL() {
|
|||
// scale IPD by sensorToWorldScale, to make the world seem larger or smaller accordingly.
|
||||
ipdScale *= sensorToWorldScale;
|
||||
|
||||
mat4 eyeProjections[2];
|
||||
{
|
||||
PROFILE_RANGE(render, "/mainRender");
|
||||
PerformanceTimer perfTimer("mainRender");
|
||||
|
@ -2770,17 +2769,8 @@ void Application::paintGL() {
|
|||
PerformanceTimer perfTimer("postComposite");
|
||||
renderArgs._batch = &postCompositeBatch;
|
||||
renderArgs._batch->setViewportTransform(ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height()));
|
||||
for_each_eye([&](Eye eye) {
|
||||
|
||||
// apply eye offset and IPD scale to the view matrix
|
||||
mat4 eyeToHead = displayPlugin->getEyeToHeadTransform(eye);
|
||||
vec3 eyeOffset = glm::vec3(eyeToHead[3]);
|
||||
mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * ipdScale);
|
||||
renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView() * eyeOffsetTransform);
|
||||
|
||||
renderArgs._batch->setProjectionTransform(eyeProjections[eye]);
|
||||
_overlays.render3DHUDOverlays(&renderArgs);
|
||||
});
|
||||
renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView());
|
||||
_overlays.render3DHUDOverlays(&renderArgs);
|
||||
}
|
||||
|
||||
auto frame = _gpuContext->endFrame();
|
||||
|
@ -3883,7 +3873,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
|||
DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
|
||||
}
|
||||
} else {
|
||||
OffscreenUi::warning("", "No location details were found in the file\n" +
|
||||
OffscreenUi::asyncWarning("", "No location details were found in the file\n" +
|
||||
snapshotPath + "\nTry dragging in an authentic Hifi snapshot.");
|
||||
}
|
||||
return true;
|
||||
|
@ -6323,7 +6313,7 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const {
|
|||
bool Application::askToSetAvatarUrl(const QString& url) {
|
||||
QUrl realUrl(url);
|
||||
if (realUrl.isLocalFile()) {
|
||||
OffscreenUi::warning("", "You can not use local files for avatar components.");
|
||||
OffscreenUi::asyncWarning("", "You can not use local files for avatar components.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -6335,41 +6325,55 @@ bool Application::askToSetAvatarUrl(const QString& url) {
|
|||
QString modelName = fstMapping["name"].toString();
|
||||
QString modelLicense = fstMapping["license"].toString();
|
||||
|
||||
bool agreeToLicence = true; // assume true
|
||||
bool agreeToLicense = true; // assume true
|
||||
//create set avatar callback
|
||||
auto setAvatar = [=] (QString url, QString modelName) {
|
||||
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Set Avatar",
|
||||
"Would you like to use '" + modelName + "' for your avatar?",
|
||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
|
||||
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
|
||||
bool ok = (QMessageBox::Ok == static_cast<QMessageBox::StandardButton>(answer.toInt()));
|
||||
if (ok) {
|
||||
getMyAvatar()->useFullAvatarURL(url, modelName);
|
||||
emit fullAvatarURLChanged(url, modelName);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Declined to use the avatar: " << url;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!modelLicense.isEmpty()) {
|
||||
// word wrap the licence text to fit in a reasonable shaped message box.
|
||||
// word wrap the license text to fit in a reasonable shaped message box.
|
||||
const int MAX_CHARACTERS_PER_LINE = 90;
|
||||
modelLicense = simpleWordWrap(modelLicense, MAX_CHARACTERS_PER_LINE);
|
||||
|
||||
agreeToLicence = QMessageBox::Yes == OffscreenUi::question("Avatar Usage License",
|
||||
modelLicense + "\nDo you agree to these terms?",
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
}
|
||||
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Avatar Usage License",
|
||||
modelLicense + "\nDo you agree to these terms?",
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
QObject::connect(dlg, &ModalDialogListener::response, this, [=, &agreeToLicense] (QVariant answer) {
|
||||
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
|
||||
bool ok = false;
|
||||
agreeToLicense = (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes);
|
||||
if (agreeToLicense) {
|
||||
switch (modelType) {
|
||||
case FSTReader::HEAD_AND_BODY_MODEL: {
|
||||
setAvatar(url, modelName);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
OffscreenUi::asyncWarning("", modelName + "Does not support a head and body as required.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Declined to agree to avatar license: " << url;
|
||||
}
|
||||
|
||||
if (!agreeToLicence) {
|
||||
qCDebug(interfaceapp) << "Declined to agree to avatar license: " << url;
|
||||
//auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
});
|
||||
} else {
|
||||
switch (modelType) {
|
||||
|
||||
case FSTReader::HEAD_AND_BODY_MODEL:
|
||||
ok = QMessageBox::Ok == OffscreenUi::question("Set Avatar",
|
||||
"Would you like to use '" + modelName + "' for your avatar?",
|
||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
|
||||
break;
|
||||
|
||||
default:
|
||||
OffscreenUi::warning("", modelName + "Does not support a head and body as required.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
getMyAvatar()->useFullAvatarURL(url, modelName);
|
||||
emit fullAvatarURLChanged(url, modelName);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Declined to use the avatar: " << url;
|
||||
setAvatar(url, modelName);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -6377,8 +6381,6 @@ bool Application::askToSetAvatarUrl(const QString& url) {
|
|||
|
||||
|
||||
bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
||||
QMessageBox::StandardButton reply;
|
||||
|
||||
QString shortName = scriptFilenameOrURL;
|
||||
|
||||
QUrl scriptURL { scriptFilenameOrURL };
|
||||
|
@ -6390,15 +6392,20 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
|||
}
|
||||
|
||||
QString message = "Would you like to run this script:\n" + shortName;
|
||||
ModalDialogListener* dlg = OffscreenUi::asyncQuestion(getWindow(), "Run Script", message,
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
reply = OffscreenUi::question(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No);
|
||||
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
const QString& fileName = scriptFilenameOrURL;
|
||||
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
|
||||
qCDebug(interfaceapp) << "Chose to run the script: " << fileName;
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(fileName);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Declined to run the script: " << scriptFilenameOrURL;
|
||||
}
|
||||
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
});
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
qCDebug(interfaceapp) << "Chose to run the script: " << scriptFilenameOrURL;
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(scriptFilenameOrURL);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Declined to run the script: " << scriptFilenameOrURL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -6435,22 +6442,26 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
|
|||
name = nameValue.toString();
|
||||
}
|
||||
|
||||
// display confirmation dialog
|
||||
if (displayAvatarAttachmentConfirmationDialog(name)) {
|
||||
|
||||
// add attachment to avatar
|
||||
auto myAvatar = getMyAvatar();
|
||||
assert(myAvatar);
|
||||
auto attachmentDataVec = myAvatar->getAttachmentData();
|
||||
AttachmentData attachmentData;
|
||||
attachmentData.fromJson(jsonObject);
|
||||
attachmentDataVec.push_back(attachmentData);
|
||||
myAvatar->setAttachmentData(attachmentDataVec);
|
||||
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "User declined to wear the avatar attachment: " << url;
|
||||
}
|
||||
|
||||
auto avatarAttachmentConfirmationTitle = tr("Avatar Attachment Confirmation");
|
||||
auto avatarAttachmentConfirmationMessage = tr("Would you like to wear '%1' on your avatar?").arg(name);
|
||||
ModalDialogListener* dlg = OffscreenUi::asyncQuestion(avatarAttachmentConfirmationTitle,
|
||||
avatarAttachmentConfirmationMessage,
|
||||
QMessageBox::Ok | QMessageBox::Cancel);
|
||||
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
|
||||
// add attachment to avatar
|
||||
auto myAvatar = getMyAvatar();
|
||||
assert(myAvatar);
|
||||
auto attachmentDataVec = myAvatar->getAttachmentData();
|
||||
AttachmentData attachmentData;
|
||||
attachmentData.fromJson(jsonObject);
|
||||
attachmentDataVec.push_back(attachmentData);
|
||||
myAvatar->setAttachmentData(attachmentDataVec);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "User declined to wear the avatar attachment: " << url;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// json parse error
|
||||
auto avatarAttachmentParseErrorString = tr("Error parsing attachment JSON from url: \"%1\"");
|
||||
|
@ -6523,20 +6534,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
|
|||
|
||||
void Application::displayAvatarAttachmentWarning(const QString& message) const {
|
||||
auto avatarAttachmentWarningTitle = tr("Avatar Attachment Failure");
|
||||
OffscreenUi::warning(avatarAttachmentWarningTitle, message);
|
||||
}
|
||||
|
||||
bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name) const {
|
||||
auto avatarAttachmentConfirmationTitle = tr("Avatar Attachment Confirmation");
|
||||
auto avatarAttachmentConfirmationMessage = tr("Would you like to wear '%1' on your avatar?").arg(name);
|
||||
auto reply = OffscreenUi::question(avatarAttachmentConfirmationTitle,
|
||||
avatarAttachmentConfirmationMessage,
|
||||
QMessageBox::Ok | QMessageBox::Cancel);
|
||||
if (QMessageBox::Ok == reply) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
OffscreenUi::asyncWarning(avatarAttachmentWarningTitle, message);
|
||||
}
|
||||
|
||||
void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const QString& name) const {
|
||||
|
@ -6858,7 +6856,7 @@ void Application::addAssetToWorldCheckModelSize() {
|
|||
if (dimensions != DEFAULT_DIMENSIONS) {
|
||||
|
||||
// Scale model so that its maximum is exactly specific size.
|
||||
const float MAXIMUM_DIMENSION = 1.0f * getMyAvatar()->getSensorToWorldScale();
|
||||
const float MAXIMUM_DIMENSION = getMyAvatar()->getSensorToWorldScale();
|
||||
auto previousDimensions = dimensions;
|
||||
auto scale = std::min(MAXIMUM_DIMENSION / dimensions.x, std::min(MAXIMUM_DIMENSION / dimensions.y,
|
||||
MAXIMUM_DIMENSION / dimensions.z));
|
||||
|
@ -7117,12 +7115,17 @@ void Application::openUrl(const QUrl& url) const {
|
|||
|
||||
void Application::loadDialog() {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
QString fileNameString = OffscreenUi::getOpenFileName(
|
||||
_glWidget, tr("Open Script"), getPreviousScriptLocation(), tr("JavaScript Files (*.js)"));
|
||||
if (!fileNameString.isEmpty() && QFile(fileNameString).exists()) {
|
||||
setPreviousScriptLocation(QFileInfo(fileNameString).absolutePath());
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(fileNameString, true, false, false, true); // Don't load from cache
|
||||
}
|
||||
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"),
|
||||
getPreviousScriptLocation(),
|
||||
tr("JavaScript Files (*.js)"));
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
const QString& response = answer.toString();
|
||||
if (!response.isEmpty() && QFile(response).exists()) {
|
||||
setPreviousScriptLocation(QFileInfo(response).absolutePath());
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(response, true, false, false, true); // Don't load from cache
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QString Application::getPreviousScriptLocation() {
|
||||
|
@ -7135,12 +7138,16 @@ void Application::setPreviousScriptLocation(const QString& location) {
|
|||
}
|
||||
|
||||
void Application::loadScriptURLDialog() const {
|
||||
QString newScript = OffscreenUi::getText(OffscreenUi::ICON_NONE, "Open and Run Script", "Script URL");
|
||||
if (QUrl(newScript).scheme() == "atp") {
|
||||
OffscreenUi::warning("Error Loading Script", "Cannot load client script over ATP");
|
||||
} else if (!newScript.isEmpty()) {
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(newScript.trimmed());
|
||||
}
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_NONE, "Open and Run Script", "Script URL");
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
const QString& newScript = response.toString();
|
||||
if (QUrl(newScript).scheme() == "atp") {
|
||||
OffscreenUi::asyncWarning("Error Loading Script", "Cannot load client script over ATP");
|
||||
} else if (!newScript.isEmpty()) {
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(newScript.trimmed());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::loadLODToolsDialog() {
|
||||
|
@ -7257,7 +7264,7 @@ void Application::notifyPacketVersionMismatch() {
|
|||
QString message = "The location you are visiting is running an incompatible server version.\n";
|
||||
message += "Content may not display properly.";
|
||||
|
||||
OffscreenUi::warning("", message);
|
||||
OffscreenUi::asyncWarning("", message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7266,7 +7273,7 @@ void Application::checkSkeleton() const {
|
|||
qCDebug(interfaceapp) << "MyAvatar model has no skeleton";
|
||||
|
||||
QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded...";
|
||||
OffscreenUi::warning("", message);
|
||||
OffscreenUi::asyncWarning("", message);
|
||||
|
||||
getMyAvatar()->useFullAvatarURL(AvatarData::defaultFullAvatarModelUrl(), DEFAULT_FULL_AVATAR_MODEL_NAME);
|
||||
} else {
|
||||
|
|
|
@ -430,7 +430,6 @@ private slots:
|
|||
|
||||
bool askToWearAvatarAttachmentUrl(const QString& url);
|
||||
void displayAvatarAttachmentWarning(const QString& message) const;
|
||||
bool displayAvatarAttachmentConfirmationDialog(const QString& name) const;
|
||||
|
||||
bool askToReplaceDomainContent(const QString& url);
|
||||
|
||||
|
|
|
@ -106,30 +106,30 @@ void AvatarBookmarks::changeToBookmarkedAvatar() {
|
|||
}
|
||||
|
||||
void AvatarBookmarks::addBookmark() {
|
||||
bool ok = false;
|
||||
auto bookmarkName = OffscreenUi::getText(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString(), &ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString());
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
auto bookmarkName = response.toString();
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
|
||||
const QVariant& avatarScale = myAvatar->getAvatarScale();
|
||||
|
||||
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
|
||||
const QVariant& avatarScale = myAvatar->getAvatarScale();
|
||||
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
|
||||
QVariantMap *bookmark = new QVariantMap;
|
||||
bookmark->insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
|
||||
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark->insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
|
||||
});
|
||||
|
||||
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
|
||||
QVariantMap *bookmark = new QVariantMap;
|
||||
bookmark->insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
|
||||
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark->insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
|
||||
}
|
||||
|
||||
void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
|
||||
|
|
|
@ -58,20 +58,25 @@ void Bookmarks::addBookmarkToFile(const QString& bookmarkName, const QVariant& b
|
|||
Menu* menubar = Menu::getInstance();
|
||||
if (contains(bookmarkName)) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto duplicateBookmarkMessage = offscreenUi->createMessageBox(OffscreenUi::ICON_WARNING, "Duplicate Bookmark",
|
||||
"The bookmark name you entered already exists in your list.",
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
duplicateBookmarkMessage->setProperty("informativeText", "Would you like to overwrite it?");
|
||||
auto result = offscreenUi->waitForMessageBoxResult(duplicateBookmarkMessage);
|
||||
if (result != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
removeBookmarkFromMenu(menubar, bookmarkName);
|
||||
}
|
||||
ModalDialogListener* dlg = OffscreenUi::asyncWarning("Duplicate Bookmark",
|
||||
"The bookmark name you entered already exists in your list.",
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
dlg->setProperty("informativeText", "Would you like to overwrite it?");
|
||||
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
|
||||
addBookmarkToMenu(menubar, bookmarkName, bookmark);
|
||||
insert(bookmarkName, bookmark); // Overwrites any item with the same bookmarkName.
|
||||
enableMenuItems(true);
|
||||
if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {
|
||||
removeBookmarkFromMenu(menubar, bookmarkName);
|
||||
addBookmarkToMenu(menubar, bookmarkName, bookmark);
|
||||
insert(bookmarkName, bookmark); // Overwrites any item with the same bookmarkName.
|
||||
enableMenuItems(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
addBookmarkToMenu(menubar, bookmarkName, bookmark);
|
||||
insert(bookmarkName, bookmark); // Overwrites any item with the same bookmarkName.
|
||||
enableMenuItems(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Bookmarks::insert(const QString& name, const QVariant& bookmark) {
|
||||
|
|
|
@ -74,20 +74,21 @@ void LocationBookmarks::teleportToBookmark() {
|
|||
}
|
||||
|
||||
void LocationBookmarks::addBookmark() {
|
||||
bool ok = false;
|
||||
auto bookmarkName = OffscreenUi::getText(OffscreenUi::ICON_PLACEMARK, "Bookmark Location", "Name", QString(), &ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Location", "Name", QString());
|
||||
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
auto bookmarkName = response.toString();
|
||||
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
QString bookmarkAddress = addressManager->currentAddress().toString();
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, bookmarkAddress);
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
QString bookmarkAddress = addressManager->currentAddress().toString();
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, bookmarkAddress);
|
||||
});
|
||||
}
|
||||
|
||||
void LocationBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& address) {
|
||||
|
@ -101,4 +102,4 @@ void LocationBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, co
|
|||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, teleportAction, name, 0, QAction::NoRole);
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -367,6 +367,20 @@ Menu::Menu() {
|
|||
QString("../../hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
|
||||
});
|
||||
|
||||
// Developer > UI >>>
|
||||
MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI");
|
||||
action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0,
|
||||
qApp->getDesktopTabletBecomesToolbarSetting());
|
||||
connect(action, &QAction::triggered, [action] {
|
||||
qApp->setDesktopTabletBecomesToolbarSetting(action->isChecked());
|
||||
});
|
||||
|
||||
action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::HMDTabletToToolbar, 0,
|
||||
qApp->getHmdTabletBecomesToolbarSetting());
|
||||
connect(action, &QAction::triggered, [action] {
|
||||
qApp->setHmdTabletBecomesToolbarSetting(action->isChecked());
|
||||
});
|
||||
|
||||
// Developer > Render >>>
|
||||
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes);
|
||||
|
|
|
@ -200,6 +200,8 @@ namespace MenuOption {
|
|||
const QString VisibleToFriends = "Friends";
|
||||
const QString VisibleToNoOne = "No one";
|
||||
const QString WorldAxes = "World Axes";
|
||||
const QString DesktopTabletToToolbar = "Desktop Tablet Becomes Toolbar";
|
||||
const QString HMDTabletToToolbar = "HMD Tablet Becomes Toolbar";
|
||||
}
|
||||
|
||||
#endif // hifi_Menu_h
|
||||
|
|
|
@ -79,7 +79,7 @@ bool ModelPackager::loadModel() {
|
|||
if (_modelFile.completeSuffix().contains("fst")) {
|
||||
QFile fst(_modelFile.filePath());
|
||||
if (!fst.open(QFile::ReadOnly | QFile::Text)) {
|
||||
OffscreenUi::warning(NULL,
|
||||
OffscreenUi::asyncWarning(NULL,
|
||||
QString("ModelPackager::loadModel()"),
|
||||
QString("Could not open FST file %1").arg(_modelFile.filePath()),
|
||||
QMessageBox::Ok);
|
||||
|
@ -98,7 +98,7 @@ bool ModelPackager::loadModel() {
|
|||
// open the fbx file
|
||||
QFile fbx(_fbxInfo.filePath());
|
||||
if (!_fbxInfo.exists() || !_fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) {
|
||||
OffscreenUi::warning(NULL,
|
||||
OffscreenUi::asyncWarning(NULL,
|
||||
QString("ModelPackager::loadModel()"),
|
||||
QString("Could not open FBX file %1").arg(_fbxInfo.filePath()),
|
||||
QMessageBox::Ok);
|
||||
|
@ -408,7 +408,7 @@ bool ModelPackager::copyTextures(const QString& oldDir, const QDir& newDir) {
|
|||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
OffscreenUi::warning(nullptr, "ModelPackager::copyTextures()",
|
||||
OffscreenUi::asyncWarning(nullptr, "ModelPackager::copyTextures()",
|
||||
"Missing textures:" + errors);
|
||||
qCDebug(interfaceapp) << "ModelPackager::copyTextures():" << errors;
|
||||
return false;
|
||||
|
|
|
@ -201,7 +201,7 @@ void ModelPropertiesDialog::chooseTextureDirectory() {
|
|||
return;
|
||||
}
|
||||
if (!directory.startsWith(_basePath)) {
|
||||
OffscreenUi::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
|
||||
OffscreenUi::asyncWarning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
|
||||
return;
|
||||
}
|
||||
_textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1));
|
||||
|
|
|
@ -42,117 +42,161 @@ static const QString COMPOUND_SHAPE_URL_KEY = "compoundShapeURL";
|
|||
static const QString MESSAGE_BOX_TITLE = "ATP Asset Migration";
|
||||
|
||||
void ATPAssetMigrator::loadEntityServerFile() {
|
||||
auto filename = OffscreenUi::getOpenFileName(_dialogParent, tr("Select an entity-server content file to migrate"), QString(), tr("Entity-Server Content (*.gz)"));
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;
|
||||
|
||||
static const QString MIGRATION_CONFIRMATION_TEXT {
|
||||
"The ATP Asset Migration process will scan the selected entity-server file,\nupload discovered resources to the"\
|
||||
" current asset-server\nand then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
|
||||
" continue?\n\nMake sure you are connected to the right domain."
|
||||
};
|
||||
|
||||
auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
|
||||
if (button == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
|
||||
// try to open the file at the given filename
|
||||
QFile modelsFile { filename };
|
||||
|
||||
if (modelsFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray compressedJsonData = modelsFile.readAll();
|
||||
QByteArray jsonData;
|
||||
|
||||
if (!gunzip(compressedJsonData, jsonData)) {
|
||||
OffscreenUi::warning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format.");
|
||||
}
|
||||
|
||||
QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData);
|
||||
_entitiesArray = modelsJSON.object()["Entities"].toArray();
|
||||
|
||||
for (auto jsonValue : _entitiesArray) {
|
||||
QJsonObject entityObject = jsonValue.toObject();
|
||||
QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();
|
||||
QString compoundURLString = entityObject.value(COMPOUND_SHAPE_URL_KEY).toString();
|
||||
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_dialogParent, tr("Select an entity-server content file to migrate"),
|
||||
QString(), tr("Entity-Server Content (*.gz)"));
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
bool isModelURL = (i == 0);
|
||||
quint8 replacementType = i;
|
||||
auto migrationURLString = (isModelURL) ? modelURLString : compoundURLString;
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
const QString& filename = response.toString();
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (!filename.isEmpty()) {
|
||||
qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;
|
||||
|
||||
if (!migrationURLString.isEmpty()) {
|
||||
QUrl migrationURL = QUrl(migrationURLString);
|
||||
auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) {
|
||||
auto request =
|
||||
DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL);
|
||||
|
||||
if (!_ignoredUrls.contains(migrationURL)
|
||||
&& (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
|
||||
|| migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) {
|
||||
if (request) {
|
||||
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";
|
||||
|
||||
if (_pendingReplacements.contains(migrationURL)) {
|
||||
// we already have a request out for this asset, just store the QJsonValueRef
|
||||
// so we can do the hash replacement when the request comes back
|
||||
_pendingReplacements.insert(migrationURL, { jsonValue, replacementType });
|
||||
} else if (_uploadedAssets.contains(migrationURL)) {
|
||||
// we already have a hash for this asset
|
||||
// so just do the replacement immediately
|
||||
if (isModelURL) {
|
||||
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
|
||||
} else {
|
||||
entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
|
||||
}
|
||||
// add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL
|
||||
// to an ATP one once ready
|
||||
_pendingReplacements.insert(migrationURL, { jsonValue, (isModelURL ? 0 : 1)});
|
||||
|
||||
jsonValue = entityObject;
|
||||
} else if (wantsToMigrateResource(migrationURL)) {
|
||||
auto request =
|
||||
DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL);
|
||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
||||
if (request->getResult() == ResourceRequest::Success) {
|
||||
migrateResource(request);
|
||||
} else {
|
||||
++_errorCount;
|
||||
_pendingReplacements.remove(migrationURL);
|
||||
qWarning() << "Could not retrieve asset at" << migrationURL.toString();
|
||||
|
||||
if (request) {
|
||||
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";
|
||||
checkIfFinished();
|
||||
}
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
// add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL
|
||||
// to an ATP one once ready
|
||||
_pendingReplacements.insert(migrationURL, { jsonValue, (isModelURL ? 0 : 1)});
|
||||
|
||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
||||
if (request->getResult() == ResourceRequest::Success) {
|
||||
migrateResource(request);
|
||||
} else {
|
||||
++_errorCount;
|
||||
_pendingReplacements.remove(migrationURL);
|
||||
qWarning() << "Could not retrieve asset at" << migrationURL.toString();
|
||||
|
||||
checkIfFinished();
|
||||
}
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->send();
|
||||
} else {
|
||||
++_errorCount;
|
||||
qWarning() << "Count not create request for asset at" << migrationURL.toString();
|
||||
}
|
||||
|
||||
} else {
|
||||
_ignoredUrls.insert(migrationURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
request->send();
|
||||
} else {
|
||||
++_errorCount;
|
||||
qWarning() << "Count not create request for asset at" << migrationURL.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_doneReading = true;
|
||||
};
|
||||
static const QString MIGRATION_CONFIRMATION_TEXT {
|
||||
"The ATP Asset Migration process will scan the selected entity-server file,\nupload discovered resources to the"\
|
||||
" current asset-server\nand then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
|
||||
" continue?\n\nMake sure you are connected to the right domain."
|
||||
};
|
||||
ModalDialogListener* migrationConfirmDialog = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
QObject::connect(migrationConfirmDialog, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
QObject::disconnect(migrationConfirmDialog, &ModalDialogListener::response, this, nullptr);
|
||||
|
||||
checkIfFinished();
|
||||
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error",
|
||||
"There was a problem loading that entity-server file for ATP asset migration. Please try again");
|
||||
if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {
|
||||
// try to open the file at the given filename
|
||||
QFile modelsFile { filename };
|
||||
|
||||
if (modelsFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray compressedJsonData = modelsFile.readAll();
|
||||
QByteArray jsonData;
|
||||
|
||||
if (!gunzip(compressedJsonData, jsonData)) {
|
||||
OffscreenUi::asyncWarning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format.");
|
||||
}
|
||||
|
||||
QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData);
|
||||
_entitiesArray = modelsJSON.object()["Entities"].toArray();
|
||||
|
||||
for (auto jsonValue : _entitiesArray) {
|
||||
QJsonObject entityObject = jsonValue.toObject();
|
||||
QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();
|
||||
QString compoundURLString = entityObject.value(COMPOUND_SHAPE_URL_KEY).toString();
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
bool isModelURL = (i == 0);
|
||||
quint8 replacementType = i;
|
||||
auto migrationURLString = (isModelURL) ? modelURLString : compoundURLString;
|
||||
|
||||
if (!migrationURLString.isEmpty()) {
|
||||
QUrl migrationURL = QUrl(migrationURLString);
|
||||
|
||||
if (!_ignoredUrls.contains(migrationURL)
|
||||
&& (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
|
||||
|| migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) {
|
||||
|
||||
if (_pendingReplacements.contains(migrationURL)) {
|
||||
// we already have a request out for this asset, just store the QJsonValueRef
|
||||
// so we can do the hash replacement when the request comes back
|
||||
_pendingReplacements.insert(migrationURL, { jsonValue, replacementType });
|
||||
} else if (_uploadedAssets.contains(migrationURL)) {
|
||||
// we already have a hash for this asset
|
||||
// so just do the replacement immediately
|
||||
if (isModelURL) {
|
||||
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
|
||||
} else {
|
||||
entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
|
||||
}
|
||||
|
||||
jsonValue = entityObject;
|
||||
} else {
|
||||
|
||||
static bool hasAskedForCompleteMigration { false };
|
||||
static bool wantsCompleteMigration { false };
|
||||
|
||||
if (!hasAskedForCompleteMigration) {
|
||||
// this is the first resource migration - ask the user if they just want to migrate everything
|
||||
static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\
|
||||
"Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\
|
||||
"Select \"No\" to be prompted for each discovered asset."
|
||||
};
|
||||
ModalDialogListener* migrationConfirmDialog1 = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE,
|
||||
"Would you like to migrate the following resource?\n" + migrationURL.toString(),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
|
||||
QObject::connect(migrationConfirmDialog1, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
QObject::disconnect(migrationConfirmDialog1, &ModalDialogListener::response, this, nullptr);
|
||||
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) ==
|
||||
QMessageBox::Yes) {
|
||||
wantsCompleteMigration = true;
|
||||
migrateResources(migrationURL, jsonValue, isModelURL);
|
||||
} else {
|
||||
ModalDialogListener* migrationConfirmDialog2 = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
|
||||
QObject::connect(migrationConfirmDialog2, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||
QObject::disconnect(migrationConfirmDialog2, &ModalDialogListener::response, this, nullptr);
|
||||
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) ==
|
||||
QMessageBox::Yes) {
|
||||
migrateResources(migrationURL, jsonValue, isModelURL);
|
||||
} else {
|
||||
_ignoredUrls.insert(migrationURL);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
hasAskedForCompleteMigration = true;
|
||||
}
|
||||
if (wantsCompleteMigration) {
|
||||
migrateResources(migrationURL, jsonValue, isModelURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_doneReading = true;
|
||||
|
||||
checkIfFinished();
|
||||
|
||||
} else {
|
||||
OffscreenUi::asyncWarning(_dialogParent, "Error",
|
||||
"There was a problem loading that entity-server file for ATP asset migration. Please try again");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ATPAssetMigrator::migrateResource(ResourceRequest* request) {
|
||||
|
@ -248,9 +292,6 @@ void ATPAssetMigrator::checkIfFinished() {
|
|||
// are we out of pending replacements? if so it is time to save the entity-server file
|
||||
if (_doneReading && _pendingReplacements.empty()) {
|
||||
saveEntityServerFile();
|
||||
|
||||
// reset after the attempted save, success or fail
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,39 +329,43 @@ bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) {
|
|||
|
||||
void ATPAssetMigrator::saveEntityServerFile() {
|
||||
// show a dialog to ask the user where they want to save the file
|
||||
QString saveName = OffscreenUi::getSaveFileName(_dialogParent, "Save Migrated Entities File");
|
||||
|
||||
QFile saveFile { saveName };
|
||||
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
QJsonObject rootObject;
|
||||
rootObject[ENTITIES_OBJECT_KEY] = _entitiesArray;
|
||||
|
||||
QJsonDocument newDocument { rootObject };
|
||||
QByteArray jsonDataForFile;
|
||||
|
||||
if (gzip(newDocument.toJson(), jsonDataForFile, -1)) {
|
||||
|
||||
saveFile.write(jsonDataForFile);
|
||||
saveFile.close();
|
||||
ModalDialogListener* dlg = OffscreenUi::getSaveFileNameAsync(_dialogParent, "Save Migrated Entities File");
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
const QString& saveName = response.toString();
|
||||
QFile saveFile { saveName };
|
||||
|
||||
QString infoMessage = QString("Your new entities file has been saved at\n%1.").arg(saveName);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
QJsonObject rootObject;
|
||||
rootObject[ENTITIES_OBJECT_KEY] = _entitiesArray;
|
||||
|
||||
if (_errorCount > 0) {
|
||||
infoMessage += QString("\nThere were %1 models that could not be migrated.\n").arg(_errorCount);
|
||||
infoMessage += "Check the warnings in your log for details.\n";
|
||||
infoMessage += "You can re-attempt migration on those models\nby restarting this process with the newly saved file.";
|
||||
QJsonDocument newDocument { rootObject };
|
||||
QByteArray jsonDataForFile;
|
||||
|
||||
if (gzip(newDocument.toJson(), jsonDataForFile, -1)) {
|
||||
|
||||
saveFile.write(jsonDataForFile);
|
||||
saveFile.close();
|
||||
|
||||
QString infoMessage = QString("Your new entities file has been saved at\n%1.").arg(saveName);
|
||||
|
||||
if (_errorCount > 0) {
|
||||
infoMessage += QString("\nThere were %1 models that could not be migrated.\n").arg(_errorCount);
|
||||
infoMessage += "Check the warnings in your log for details.\n";
|
||||
infoMessage += "You can re-attempt migration on those models\nby restarting this process with the newly saved file.";
|
||||
}
|
||||
|
||||
OffscreenUi::asyncInformation(_dialogParent, "Success", infoMessage);
|
||||
} else {
|
||||
OffscreenUi::asyncWarning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");
|
||||
}
|
||||
|
||||
OffscreenUi::information(_dialogParent, "Success", infoMessage);
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");
|
||||
OffscreenUi::asyncWarning(_dialogParent, "Error",
|
||||
QString("Could not open file at %1 to write new entities file to.").arg(saveName));
|
||||
}
|
||||
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error",
|
||||
QString("Could not open file at %1 to write new entities file to.").arg(saveName));
|
||||
}
|
||||
// reset after the attempted save, success or fail
|
||||
reset();
|
||||
});
|
||||
}
|
||||
|
||||
void ATPAssetMigrator::reset() {
|
||||
|
|
|
@ -3043,9 +3043,9 @@ glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const {
|
|||
if (rightEyeIndex >= 0 && leftEyeIndex >= 0) {
|
||||
auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f;
|
||||
auto centerEyeRot = Quaternions::Y_180;
|
||||
return createMatFromQuatAndPos(centerEyeRot, centerEyePos);
|
||||
return createMatFromQuatAndPos(centerEyeRot, centerEyePos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, DEFAULT_AVATAR_MIDDLE_EYE_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, DEFAULT_AVATAR_MIDDLE_EYE_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3055,9 +3055,9 @@ glm::mat4 MyAvatar::getHeadCalibrationMat() const {
|
|||
if (headIndex >= 0) {
|
||||
auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex);
|
||||
auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex);
|
||||
return createMatFromQuatAndPos(headRot, headPos);
|
||||
return createMatFromQuatAndPos(headRot, headPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3067,9 +3067,9 @@ glm::mat4 MyAvatar::getSpine2CalibrationMat() const {
|
|||
if (spine2Index >= 0) {
|
||||
auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index);
|
||||
auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index);
|
||||
return createMatFromQuatAndPos(spine2Rot, spine2Pos);
|
||||
return createMatFromQuatAndPos(spine2Rot, spine2Pos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3079,9 +3079,9 @@ glm::mat4 MyAvatar::getHipsCalibrationMat() const {
|
|||
if (hipsIndex >= 0) {
|
||||
auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex);
|
||||
auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex);
|
||||
return createMatFromQuatAndPos(hipsRot, hipsPos);
|
||||
return createMatFromQuatAndPos(hipsRot, hipsPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3091,9 +3091,9 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const {
|
|||
if (leftFootIndex >= 0) {
|
||||
auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex);
|
||||
auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex);
|
||||
return createMatFromQuatAndPos(leftFootRot, leftFootPos);
|
||||
return createMatFromQuatAndPos(leftFootRot, leftFootPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3103,9 +3103,9 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const {
|
|||
if (rightFootIndex >= 0) {
|
||||
auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex);
|
||||
auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex);
|
||||
return createMatFromQuatAndPos(rightFootRot, rightFootPos);
|
||||
return createMatFromQuatAndPos(rightFootRot, rightFootPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3115,9 +3115,9 @@ glm::mat4 MyAvatar::getRightArmCalibrationMat() const {
|
|||
if (rightArmIndex >= 0) {
|
||||
auto rightArmPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightArmIndex);
|
||||
auto rightArmRot = getAbsoluteDefaultJointRotationInObjectFrame(rightArmIndex);
|
||||
return createMatFromQuatAndPos(rightArmRot, rightArmPos);
|
||||
return createMatFromQuatAndPos(rightArmRot, rightArmPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3126,9 +3126,9 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const {
|
|||
if (leftArmIndex >= 0) {
|
||||
auto leftArmPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftArmIndex);
|
||||
auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex);
|
||||
return createMatFromQuatAndPos(leftArmRot, leftArmPos);
|
||||
return createMatFromQuatAndPos(leftArmRot, leftArmPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3137,9 +3137,9 @@ glm::mat4 MyAvatar::getRightHandCalibrationMat() const {
|
|||
if (rightHandIndex >= 0) {
|
||||
auto rightHandPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightHandIndex);
|
||||
auto rightHandRot = getAbsoluteDefaultJointRotationInObjectFrame(rightHandIndex);
|
||||
return createMatFromQuatAndPos(rightHandRot, rightHandPos);
|
||||
return createMatFromQuatAndPos(rightHandRot, rightHandPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3148,9 +3148,9 @@ glm::mat4 MyAvatar::getLeftHandCalibrationMat() const {
|
|||
if (leftHandIndex >= 0) {
|
||||
auto leftHandPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftHandIndex);
|
||||
auto leftHandRot = getAbsoluteDefaultJointRotationInObjectFrame(leftHandIndex);
|
||||
return createMatFromQuatAndPos(leftHandRot, leftHandPos);
|
||||
return createMatFromQuatAndPos(leftHandRot, leftHandPos / getSensorToWorldScale());
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS / getSensorToWorldScale());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,8 +92,7 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant&
|
|||
}
|
||||
}
|
||||
|
||||
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState) {
|
||||
PickRay pickRay = qApp->getRayPickManager().getPickRay(_rayPickUID);
|
||||
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState) {
|
||||
if (!renderState.getStartID().isNull()) {
|
||||
QVariantMap startProps;
|
||||
startProps.insert("position", vec3toVariant(pickRay.origin));
|
||||
|
@ -185,12 +184,14 @@ void LaserPointer::disableRenderState(const RenderState& renderState) {
|
|||
|
||||
void LaserPointer::update() {
|
||||
RayPickResult prevRayPickResult = DependencyManager::get<RayPickScriptingInterface>()->getPrevRayPickResult(_rayPickUID);
|
||||
if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && prevRayPickResult.type != IntersectionType::NONE) {
|
||||
updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, prevRayPickResult.distance, prevRayPickResult.objectID, false);
|
||||
if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
|
||||
(prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) {
|
||||
float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult.distance;
|
||||
updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, distance, prevRayPickResult.objectID, prevRayPickResult.searchRay, false);
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
} else if (_renderingEnabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
disableRenderState(_renderStates[_currentRenderState]);
|
||||
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), true);
|
||||
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), prevRayPickResult.searchRay, true);
|
||||
} else if (!_currentRenderState.empty()) {
|
||||
disableRenderState(_renderStates[_currentRenderState]);
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
|
|
|
@ -65,6 +65,7 @@ public:
|
|||
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps);
|
||||
|
||||
void setPrecisionPicking(const bool precisionPicking) { DependencyManager::get<RayPickScriptingInterface>()->setPrecisionPicking(_rayPickUID, precisionPicking); }
|
||||
void setLaserLength(const float laserLength) { _laserLength = laserLength; }
|
||||
void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get<RayPickScriptingInterface>()->setIgnoreEntities(_rayPickUID, ignoreEntities); }
|
||||
void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get<RayPickScriptingInterface>()->setIncludeEntities(_rayPickUID, includeEntities); }
|
||||
void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get<RayPickScriptingInterface>()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); }
|
||||
|
@ -78,6 +79,7 @@ public:
|
|||
|
||||
private:
|
||||
bool _renderingEnabled;
|
||||
float _laserLength { 0.0f };
|
||||
std::string _currentRenderState { "" };
|
||||
RenderStateMap _renderStates;
|
||||
DefaultRenderStateMap _defaultRenderStates;
|
||||
|
@ -89,7 +91,7 @@ private:
|
|||
QUuid _rayPickUID;
|
||||
|
||||
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
|
||||
void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState);
|
||||
void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState);
|
||||
void disableRenderState(const RenderState& renderState);
|
||||
|
||||
};
|
||||
|
|
|
@ -14,17 +14,19 @@ QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const La
|
|||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) {
|
||||
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled);
|
||||
if (!laserPointer->getRayUID().isNull()) {
|
||||
QWriteLocker lock(&_addLock);
|
||||
QWriteLocker containsLock(&_containsLock);
|
||||
QUuid id = QUuid::createUuid();
|
||||
_laserPointersToAdd.push(std::pair<QUuid, std::shared_ptr<LaserPointer>>(id, laserPointer));
|
||||
_laserPointers[id] = laserPointer;
|
||||
_laserPointerLocks[id] = std::make_shared<QReadWriteLock>();
|
||||
return id;
|
||||
}
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
void LaserPointerManager::removeLaserPointer(const QUuid uid) {
|
||||
QWriteLocker lock(&_removeLock);
|
||||
_laserPointersToRemove.push(uid);
|
||||
QWriteLocker lock(&_containsLock);
|
||||
_laserPointers.remove(uid);
|
||||
_laserPointerLocks.remove(uid);
|
||||
}
|
||||
|
||||
void LaserPointerManager::enableLaserPointer(const QUuid uid) {
|
||||
|
@ -69,32 +71,12 @@ const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) {
|
|||
}
|
||||
|
||||
void LaserPointerManager::update() {
|
||||
QReadLocker lock(&_containsLock);
|
||||
for (QUuid& uid : _laserPointers.keys()) {
|
||||
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
|
||||
QReadLocker laserLock(_laserPointerLocks[uid].get());
|
||||
_laserPointers[uid]->update();
|
||||
}
|
||||
|
||||
QWriteLocker containsLock(&_containsLock);
|
||||
{
|
||||
QWriteLocker lock(&_addLock);
|
||||
while (!_laserPointersToAdd.empty()) {
|
||||
std::pair<QUuid, std::shared_ptr<LaserPointer>> laserPointerToAdd = _laserPointersToAdd.front();
|
||||
_laserPointersToAdd.pop();
|
||||
_laserPointers[laserPointerToAdd.first] = laserPointerToAdd.second;
|
||||
_laserPointerLocks[laserPointerToAdd.first] = std::make_shared<QReadWriteLock>();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QWriteLocker lock(&_removeLock);
|
||||
while (!_laserPointersToRemove.empty()) {
|
||||
QUuid uid = _laserPointersToRemove.front();
|
||||
_laserPointersToRemove.pop();
|
||||
_laserPointers.remove(uid);
|
||||
_laserPointerLocks.remove(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) {
|
||||
|
@ -105,6 +87,14 @@ void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPic
|
|||
}
|
||||
}
|
||||
|
||||
void LaserPointerManager::setLaserLength(QUuid uid, const float laserLength) {
|
||||
QReadLocker lock(&_containsLock);
|
||||
if (_laserPointers.contains(uid)) {
|
||||
QWriteLocker laserLock(_laserPointerLocks[uid].get());
|
||||
_laserPointers[uid]->setLaserLength(laserLength);
|
||||
}
|
||||
}
|
||||
|
||||
void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) {
|
||||
QReadLocker lock(&_containsLock);
|
||||
if (_laserPointers.contains(uid)) {
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
const RayPickResult getPrevRayPickResult(const QUuid uid);
|
||||
|
||||
void setPrecisionPicking(QUuid uid, const bool precisionPicking);
|
||||
void setLaserLength(QUuid uid, const float laserLength);
|
||||
void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities);
|
||||
void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities);
|
||||
void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays);
|
||||
|
@ -46,10 +47,6 @@ public:
|
|||
private:
|
||||
QHash<QUuid, std::shared_ptr<LaserPointer>> _laserPointers;
|
||||
QHash<QUuid, std::shared_ptr<QReadWriteLock>> _laserPointerLocks;
|
||||
QReadWriteLock _addLock;
|
||||
std::queue<std::pair<QUuid, std::shared_ptr<LaserPointer>>> _laserPointersToAdd;
|
||||
QReadWriteLock _removeLock;
|
||||
std::queue<QUuid> _laserPointersToRemove;
|
||||
QReadWriteLock _containsLock;
|
||||
|
||||
};
|
||||
|
|
|
@ -31,6 +31,7 @@ public slots:
|
|||
Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid) { return qApp->getLaserPointerManager().getPrevRayPickResult(uid); }
|
||||
|
||||
Q_INVOKABLE void setPrecisionPicking(QUuid uid, const bool precisionPicking) { qApp->getLaserPointerManager().setPrecisionPicking(uid, precisionPicking); }
|
||||
Q_INVOKABLE void setLaserLength(QUuid uid, const float laserLength) { qApp->getLaserPointerManager().setLaserLength(uid, laserLength); }
|
||||
Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { qApp->getLaserPointerManager().setIgnoreEntities(uid, ignoreEntities); }
|
||||
Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { qApp->getLaserPointerManager().setIncludeEntities(uid, includeEntities); }
|
||||
Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { qApp->getLaserPointerManager().setIgnoreOverlays(uid, ignoreOverlays); }
|
||||
|
|
|
@ -70,6 +70,9 @@ public:
|
|||
if (doesPickNonCollidable()) {
|
||||
toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE);
|
||||
}
|
||||
if (doesPickCourse()) {
|
||||
toReturn |= getBitMask(PICK_COURSE);
|
||||
}
|
||||
return Flags(toReturn);
|
||||
}
|
||||
Flags getOverlayFlags() const {
|
||||
|
@ -80,6 +83,9 @@ public:
|
|||
if (doesPickNonCollidable()) {
|
||||
toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE);
|
||||
}
|
||||
if (doesPickCourse()) {
|
||||
toReturn |= getBitMask(PICK_COURSE);
|
||||
}
|
||||
return Flags(toReturn);
|
||||
}
|
||||
Flags getAvatarFlags() const { return Flags(getBitMask(PICK_AVATARS)); }
|
||||
|
|
|
@ -38,11 +38,12 @@ void RayPickManager::cacheResult(const bool intersects, const RayPickResult& res
|
|||
res = resTemp;
|
||||
}
|
||||
} else {
|
||||
cache[ray][mask] = RayPickResult();
|
||||
cache[ray][mask] = RayPickResult(res.searchRay);
|
||||
}
|
||||
}
|
||||
|
||||
void RayPickManager::update() {
|
||||
QReadLocker lock(&_containsLock);
|
||||
RayPickCache results;
|
||||
for (auto& uid : _rayPicks.keys()) {
|
||||
std::shared_ptr<RayPick> rayPick = _rayPicks[uid];
|
||||
|
@ -58,7 +59,7 @@ void RayPickManager::update() {
|
|||
}
|
||||
|
||||
QPair<glm::vec3, glm::vec3> rayKey = QPair<glm::vec3, glm::vec3>(ray.origin, ray.direction);
|
||||
RayPickResult res;
|
||||
RayPickResult res = RayPickResult(ray);
|
||||
|
||||
if (rayPick->getFilter().doesPickEntities()) {
|
||||
RayToEntityIntersectionResult entityRes;
|
||||
|
@ -73,7 +74,7 @@ void RayPickManager::update() {
|
|||
}
|
||||
|
||||
if (!fromCache) {
|
||||
cacheResult(entityRes.intersects, RayPickResult(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, entityRes.surfaceNormal),
|
||||
cacheResult(entityRes.intersects, RayPickResult(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, ray, entityRes.surfaceNormal),
|
||||
entityMask, res, rayKey, results);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ void RayPickManager::update() {
|
|||
}
|
||||
|
||||
if (!fromCache) {
|
||||
cacheResult(overlayRes.intersects, RayPickResult(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, overlayRes.surfaceNormal),
|
||||
cacheResult(overlayRes.intersects, RayPickResult(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, ray, overlayRes.surfaceNormal),
|
||||
overlayMask, res, rayKey, results);
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +101,7 @@ void RayPickManager::update() {
|
|||
RayPickFilter::Flags avatarMask = rayPick->getFilter().getAvatarFlags();
|
||||
if (!checkAndCompareCachedResults(rayKey, results, res, avatarMask)) {
|
||||
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(ray, rayPick->getIncludeAvatars(), rayPick->getIgnoreAvatars());
|
||||
cacheResult(avatarRes.intersects, RayPickResult(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection), avatarMask, res, rayKey, results);
|
||||
cacheResult(avatarRes.intersects, RayPickResult(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, ray), avatarMask, res, rayKey, results);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +110,7 @@ void RayPickManager::update() {
|
|||
RayPickFilter::Flags hudMask = rayPick->getFilter().getHUDFlags();
|
||||
if (!checkAndCompareCachedResults(rayKey, results, res, hudMask)) {
|
||||
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateRayUICollisionPoint(ray.origin, ray.direction);
|
||||
cacheResult(true, RayPickResult(IntersectionType::HUD, 0, glm::distance(ray.origin, hudRes), hudRes), hudMask, res, rayKey, results);
|
||||
cacheResult(true, RayPickResult(IntersectionType::HUD, 0, glm::distance(ray.origin, hudRes), hudRes, ray), hudMask, res, rayKey, results);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,56 +118,39 @@ void RayPickManager::update() {
|
|||
if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) {
|
||||
rayPick->setRayPickResult(res);
|
||||
} else {
|
||||
rayPick->setRayPickResult(RayPickResult());
|
||||
}
|
||||
}
|
||||
|
||||
QWriteLocker containsLock(&_containsLock);
|
||||
{
|
||||
QWriteLocker lock(&_addLock);
|
||||
while (!_rayPicksToAdd.empty()) {
|
||||
std::pair<QUuid, std::shared_ptr<RayPick>> rayPickToAdd = _rayPicksToAdd.front();
|
||||
_rayPicksToAdd.pop();
|
||||
_rayPicks[rayPickToAdd.first] = rayPickToAdd.second;
|
||||
_rayPickLocks[rayPickToAdd.first] = std::make_shared<QReadWriteLock>();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QWriteLocker lock(&_removeLock);
|
||||
while (!_rayPicksToRemove.empty()) {
|
||||
QUuid uid = _rayPicksToRemove.front();
|
||||
_rayPicksToRemove.pop();
|
||||
_rayPicks.remove(uid);
|
||||
_rayPickLocks.remove(uid);
|
||||
rayPick->setRayPickResult(RayPickResult(ray));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QUuid RayPickManager::createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled) {
|
||||
QWriteLocker lock(&_addLock);
|
||||
QWriteLocker lock(&_containsLock);
|
||||
QUuid id = QUuid::createUuid();
|
||||
_rayPicksToAdd.push(std::pair<QUuid, std::shared_ptr<RayPick>>(id, std::make_shared<JointRayPick>(jointName, posOffset, dirOffset, filter, maxDistance, enabled)));
|
||||
_rayPicks[id] = std::make_shared<JointRayPick>(jointName, posOffset, dirOffset, filter, maxDistance, enabled);
|
||||
_rayPickLocks[id] = std::make_shared<QReadWriteLock>();
|
||||
return id;
|
||||
}
|
||||
|
||||
QUuid RayPickManager::createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) {
|
||||
QWriteLocker lock(&_addLock);
|
||||
QWriteLocker lock(&_containsLock);
|
||||
QUuid id = QUuid::createUuid();
|
||||
_rayPicksToAdd.push(std::pair<QUuid, std::shared_ptr<RayPick>>(id, std::make_shared<MouseRayPick>(filter, maxDistance, enabled)));
|
||||
_rayPicks[id] = std::make_shared<MouseRayPick>(filter, maxDistance, enabled);
|
||||
_rayPickLocks[id] = std::make_shared<QReadWriteLock>();
|
||||
return id;
|
||||
}
|
||||
|
||||
QUuid RayPickManager::createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled) {
|
||||
QWriteLocker lock(&_addLock);
|
||||
QWriteLocker lock(&_containsLock);
|
||||
QUuid id = QUuid::createUuid();
|
||||
_rayPicksToAdd.push(std::pair<QUuid, std::shared_ptr<RayPick>>(id, std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled)));
|
||||
_rayPicks[id] = std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled);
|
||||
_rayPickLocks[id] = std::make_shared<QReadWriteLock>();
|
||||
return id;
|
||||
}
|
||||
|
||||
void RayPickManager::removeRayPick(const QUuid uid) {
|
||||
QWriteLocker lock(&_removeLock);
|
||||
_rayPicksToRemove.push(uid);
|
||||
QWriteLocker lock(&_containsLock);
|
||||
_rayPicks.remove(uid);
|
||||
_rayPickLocks.remove(uid);
|
||||
}
|
||||
|
||||
void RayPickManager::enableRayPick(const QUuid uid) {
|
||||
|
@ -185,18 +169,6 @@ void RayPickManager::disableRayPick(const QUuid uid) {
|
|||
}
|
||||
}
|
||||
|
||||
const PickRay RayPickManager::getPickRay(const QUuid uid) {
|
||||
QReadLocker containsLock(&_containsLock);
|
||||
if (_rayPicks.contains(uid)) {
|
||||
bool valid;
|
||||
PickRay pickRay = _rayPicks[uid]->getPickRay(valid);
|
||||
if (valid) {
|
||||
return pickRay;
|
||||
}
|
||||
}
|
||||
return PickRay();
|
||||
}
|
||||
|
||||
const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) {
|
||||
QReadLocker containsLock(&_containsLock);
|
||||
if (_rayPicks.contains(uid)) {
|
||||
|
|
|
@ -28,7 +28,6 @@ class RayPickManager {
|
|||
|
||||
public:
|
||||
void update();
|
||||
const PickRay getPickRay(const QUuid uid);
|
||||
|
||||
QUuid createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled);
|
||||
QUuid createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled);
|
||||
|
@ -49,10 +48,6 @@ public:
|
|||
private:
|
||||
QHash<QUuid, std::shared_ptr<RayPick>> _rayPicks;
|
||||
QHash<QUuid, std::shared_ptr<QReadWriteLock>> _rayPickLocks;
|
||||
QReadWriteLock _addLock;
|
||||
std::queue<std::pair<QUuid, std::shared_ptr<RayPick>>> _rayPicksToAdd;
|
||||
QReadWriteLock _removeLock;
|
||||
std::queue<QUuid> _rayPicksToRemove;
|
||||
QReadWriteLock _containsLock;
|
||||
|
||||
typedef QHash<QPair<glm::vec3, glm::vec3>, std::unordered_map<RayPickFilter::Flags, RayPickResult>> RayPickCache;
|
||||
|
|
|
@ -59,7 +59,7 @@ WindowScriptingInterface::WindowScriptingInterface() {
|
|||
QUrl url(urlString);
|
||||
emit svoImportRequested(url.url());
|
||||
} else {
|
||||
OffscreenUi::warning("Import SVO Error", "You need to be running edit.js to import entities.");
|
||||
OffscreenUi::asyncWarning("Import SVO Error", "You need to be running edit.js to import entities.");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -103,7 +103,7 @@ void WindowScriptingInterface::raiseMainWindow() {
|
|||
/// \param const QString& message message to display
|
||||
/// \return QScriptValue::UndefinedValue
|
||||
void WindowScriptingInterface::alert(const QString& message) {
|
||||
OffscreenUi::warning("", message);
|
||||
OffscreenUi::asyncWarning("", message);
|
||||
}
|
||||
|
||||
/// Display a confirmation box with the options 'Yes' and 'No'
|
||||
|
@ -123,6 +123,17 @@ QScriptValue WindowScriptingInterface::prompt(const QString& message, const QStr
|
|||
return ok ? QScriptValue(result) : QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
/// Display a prompt with a text box
|
||||
/// \param const QString& message message to display
|
||||
/// \param const QString& defaultText default text in the text box
|
||||
void WindowScriptingInterface::promptAsync(const QString& message, const QString& defaultText) {
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(nullptr, "", message, QLineEdit::Normal, defaultText);
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant result) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
emit promptTextChanged(result.toString());
|
||||
});
|
||||
}
|
||||
|
||||
CustomPromptResult WindowScriptingInterface::customPrompt(const QVariant& config) {
|
||||
CustomPromptResult result;
|
||||
bool ok = false;
|
||||
|
@ -191,8 +202,31 @@ QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QSt
|
|||
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
|
||||
}
|
||||
|
||||
/// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
void WindowScriptingInterface::browseDirAsync(const QString& title, const QString& directory) {
|
||||
ensureReticleVisible();
|
||||
QString path = directory;
|
||||
if (path.isEmpty()) {
|
||||
path = getPreviousBrowseLocation();
|
||||
}
|
||||
#ifndef Q_OS_WIN
|
||||
path = fixupPathForMac(directory);
|
||||
#endif
|
||||
ModalDialogListener* dlg = OffscreenUi::getExistingDirectoryAsync(nullptr, title, path);
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
const QString& result = response.toString();
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (!result.isEmpty()) {
|
||||
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
|
||||
}
|
||||
emit browseDirChanged(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
|
@ -213,6 +247,31 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin
|
|||
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
|
||||
}
|
||||
|
||||
/// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
void WindowScriptingInterface::browseAsync(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
ensureReticleVisible();
|
||||
QString path = directory;
|
||||
if (path.isEmpty()) {
|
||||
path = getPreviousBrowseLocation();
|
||||
}
|
||||
#ifndef Q_OS_WIN
|
||||
path = fixupPathForMac(directory);
|
||||
#endif
|
||||
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(nullptr, title, path, nameFilter);
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
const QString& result = response.toString();
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (!result.isEmpty()) {
|
||||
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
|
||||
}
|
||||
emit openFileChanged(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
|
@ -235,7 +294,32 @@ QScriptValue WindowScriptingInterface::save(const QString& title, const QString&
|
|||
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
|
||||
}
|
||||
|
||||
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
|
||||
/// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
void WindowScriptingInterface::saveAsync(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
ensureReticleVisible();
|
||||
QString path = directory;
|
||||
if (path.isEmpty()) {
|
||||
path = getPreviousBrowseLocation();
|
||||
}
|
||||
#ifndef Q_OS_WIN
|
||||
path = fixupPathForMac(directory);
|
||||
#endif
|
||||
ModalDialogListener* dlg = OffscreenUi::getSaveFileNameAsync(nullptr, title, path, nameFilter);
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
const QString& result = response.toString();
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (!result.isEmpty()) {
|
||||
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
|
||||
}
|
||||
emit saveFileChanged(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
|
||||
/// directory the browser will start at the root directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the asset browser at
|
||||
|
@ -260,6 +344,35 @@ QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const
|
|||
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
|
||||
}
|
||||
|
||||
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
|
||||
/// directory the browser will start at the root directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the asset browser at
|
||||
/// \param const QString& nameFilter filter to filter asset names by - see `QFileDialog`
|
||||
void WindowScriptingInterface::browseAssetsAsync(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
ensureReticleVisible();
|
||||
QString path = directory;
|
||||
if (path.isEmpty()) {
|
||||
path = getPreviousBrowseAssetLocation();
|
||||
}
|
||||
if (path.left(1) != "/") {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (path.right(1) != "/") {
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
ModalDialogListener* dlg = OffscreenUi::getOpenAssetNameAsync(nullptr, title, path, nameFilter);
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
const QString& result = response.toString();
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (!result.isEmpty()) {
|
||||
setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath());
|
||||
}
|
||||
emit assetsDirChanged(result);
|
||||
});
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::showAssetServer(const QString& upload) {
|
||||
QMetaObject::invokeMethod(qApp, "showAssetServerWidget", Qt::QueuedConnection, Q_ARG(QString, upload));
|
||||
}
|
||||
|
|
|
@ -51,12 +51,17 @@ public slots:
|
|||
void raiseMainWindow();
|
||||
void alert(const QString& message = "");
|
||||
QScriptValue confirm(const QString& message = "");
|
||||
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
|
||||
QScriptValue prompt(const QString& message, const QString& defaultText);
|
||||
void promptAsync(const QString& message = "", const QString& defaultText = "");
|
||||
CustomPromptResult customPrompt(const QVariant& config);
|
||||
QScriptValue browseDir(const QString& title = "", const QString& directory = "");
|
||||
void browseDirAsync(const QString& title = "", const QString& directory = "");
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
void browseAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
void saveAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
void browseAssetsAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
void showAssetServer(const QString& upload = "");
|
||||
QString checkVersion();
|
||||
void copyToClipboard(const QString& text);
|
||||
|
@ -89,6 +94,11 @@ signals:
|
|||
void announcement(const QString& message);
|
||||
|
||||
void messageBoxClosed(int id, int button);
|
||||
void browseDirChanged(QString browseDir);
|
||||
void assetsDirChanged(QString assetsDir);
|
||||
void saveFileChanged(QString filename);
|
||||
void openFileChanged(QString filename);
|
||||
void promptTextChanged(QString text);
|
||||
|
||||
// triggered when window size or position changes
|
||||
void geometryChanged(QRect geometry);
|
||||
|
|
|
@ -73,11 +73,11 @@ void AddressBarDialog::loadForward() {
|
|||
}
|
||||
|
||||
void AddressBarDialog::displayAddressOfflineMessage() {
|
||||
OffscreenUi::critical("", "That user or place is currently offline");
|
||||
OffscreenUi::asyncCritical("", "That user or place is currently offline");
|
||||
}
|
||||
|
||||
void AddressBarDialog::displayAddressNotFoundMessage() {
|
||||
OffscreenUi::critical("", "There is no address information for that user or place");
|
||||
OffscreenUi::asyncCritical("", "There is no address information for that user or place");
|
||||
}
|
||||
|
||||
void AddressBarDialog::observeShownChanged(bool visible) {
|
||||
|
|
|
@ -91,16 +91,6 @@ void setupPreferences() {
|
|||
preference->setMax(500);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getDesktopTabletBecomesToolbarSetting(); };
|
||||
auto setter = [](bool value) { qApp->setDesktopTabletBecomesToolbarSetting(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Desktop Tablet Becomes Toolbar", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getHmdTabletBecomesToolbarSetting(); };
|
||||
auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
|
||||
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
|
||||
|
|
|
@ -280,7 +280,9 @@ ModelOverlay* ModelOverlay::createClone() const {
|
|||
}
|
||||
|
||||
Transform ModelOverlay::evalRenderTransform() {
|
||||
return getTransform();
|
||||
Transform transform = getTransform();
|
||||
transform.setScale(1.0f); // disable inherited scale
|
||||
return transform;
|
||||
}
|
||||
|
||||
void ModelOverlay::locationChanged(bool tellPhysics) {
|
||||
|
|
|
@ -39,7 +39,6 @@ void Sphere3DOverlay::render(RenderArgs* args) {
|
|||
auto batch = args->_batch;
|
||||
|
||||
if (batch) {
|
||||
|
||||
batch->setModelTransform(getRenderTransform());
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
@ -72,11 +71,8 @@ Sphere3DOverlay* Sphere3DOverlay::createClone() const {
|
|||
}
|
||||
|
||||
Transform Sphere3DOverlay::evalRenderTransform() {
|
||||
// FIXME Start using the _renderTransform instead of calling for Transform and Dimensions from here, do the custom things needed in evalRenderTransform()
|
||||
Transform transform = getTransform();
|
||||
#ifndef USE_SN_SCALE
|
||||
transform.setScale(1.0f); // ignore inherited scale from SpatiallyNestable
|
||||
#endif
|
||||
transform.postScale(getDimensions() * SPHERE_OVERLAY_SCALE);
|
||||
|
||||
return transform;
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <RenderDeferredTask.h>
|
||||
#include <TextRenderer3D.h>
|
||||
|
||||
#include <AbstractViewStateInterface.h>
|
||||
|
||||
const int FIXED_FONT_POINT_SIZE = 40;
|
||||
const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 80.0f; // this is a ratio determined through experimentation
|
||||
const float LINE_SCALE_RATIO = 1.2f;
|
||||
|
@ -111,6 +113,7 @@ void Text3DOverlay::render(RenderArgs* args) {
|
|||
|
||||
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
|
||||
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false, false);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, quadColor, _geometryId);
|
||||
|
||||
// Same font properties as textSize()
|
||||
|
@ -130,13 +133,8 @@ void Text3DOverlay::render(RenderArgs* args) {
|
|||
glm::vec4 textColor = { _color.red / MAX_COLOR, _color.green / MAX_COLOR,
|
||||
_color.blue / MAX_COLOR, getTextAlpha() };
|
||||
|
||||
// FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline
|
||||
// for a gpu performance increase. Currently,
|
||||
// Text renderer sets its own pipeline,
|
||||
_textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f), getDrawInFront());
|
||||
// so before we continue, we must reset the pipeline
|
||||
batch.setPipeline(args->_shapePipeline->pipeline);
|
||||
args->_shapePipeline->prepare(batch, args);
|
||||
// FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline for a gpu performance increase.
|
||||
_textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f), true);
|
||||
}
|
||||
|
||||
const render::ShapeKey Text3DOverlay::getShapeKey() {
|
||||
|
@ -157,7 +155,18 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
|
||||
auto textAlpha = properties["textAlpha"];
|
||||
if (textAlpha.isValid()) {
|
||||
float prevTextAlpha = getTextAlpha();
|
||||
setTextAlpha(textAlpha.toFloat());
|
||||
// Update our payload key if necessary to handle transparency
|
||||
if ((prevTextAlpha < 1.0f && _textAlpha >= 1.0f) || (prevTextAlpha >= 1.0f && _textAlpha < 1.0f)) {
|
||||
auto itemID = getRenderItemID();
|
||||
if (render::Item::isValidID(itemID)) {
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
transaction.updateItem(itemID);
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool valid;
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
xColor getBackgroundColor();
|
||||
float getTextAlpha() { return _textAlpha; }
|
||||
float getBackgroundAlpha() { return getAlpha(); }
|
||||
bool isTransparent() override { return Overlay::isTransparent() || _textAlpha < 1.0f; }
|
||||
|
||||
// setters
|
||||
void setText(const QString& text);
|
||||
|
|
|
@ -62,9 +62,7 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
|
|||
// extents is the entity relative, scaled, centered extents of the entity
|
||||
glm::mat4 worldToEntityMatrix;
|
||||
Transform transform = getTransform();
|
||||
#ifndef USE_SN_SCALE
|
||||
transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable
|
||||
#endif
|
||||
transform.getInverseMatrix(worldToEntityMatrix);
|
||||
|
||||
glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
|
|
|
@ -302,7 +302,6 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
emit resizeWebSurface();
|
||||
}
|
||||
|
||||
vec2 halfSize = getSize() / 2.0f;
|
||||
vec4 color(toGlm(getColor()), getAlpha());
|
||||
|
||||
if (!_texture) {
|
||||
|
@ -318,8 +317,11 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setResourceTexture(0, _texture);
|
||||
|
||||
auto renderTransform = getRenderTransform();
|
||||
batch.setModelTransform(getRenderTransform());
|
||||
auto size = renderTransform.getScale();
|
||||
renderTransform.setScale(1.0f);
|
||||
batch.setModelTransform(renderTransform);
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
|
||||
|
@ -327,10 +329,19 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
} else {
|
||||
geometryCache->bindWebBrowserProgram(batch);
|
||||
}
|
||||
|
||||
vec2 halfSize = vec2(size.x, size.y) / 2.0f;
|
||||
geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color, _geometryId);
|
||||
batch.setResourceTexture(0, nullptr); // restore default white color after me
|
||||
}
|
||||
|
||||
Transform Web3DOverlay::evalRenderTransform() {
|
||||
Transform transform = Parent::evalRenderTransform();
|
||||
transform.setScale(1.0f);
|
||||
transform.postScale(glm::vec3(getSize(), 1.0f));
|
||||
return transform;
|
||||
}
|
||||
|
||||
const render::ShapeKey Web3DOverlay::getShapeKey() {
|
||||
auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline();
|
||||
if (isTransparent()) {
|
||||
|
|
|
@ -78,6 +78,9 @@ signals:
|
|||
void requestWebSurface();
|
||||
void releaseWebSurface();
|
||||
|
||||
protected:
|
||||
Transform evalRenderTransform() override;
|
||||
|
||||
private:
|
||||
InputMode _inputMode { Touch };
|
||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||
|
|
|
@ -441,7 +441,7 @@ void HmdDisplayPlugin::OverlayRenderer::updatePipeline() {
|
|||
this->uniformsLocation = program->getUniformBuffers().findLocation("overlayBuffer");
|
||||
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL));
|
||||
state->setDepthTest(gpu::State::DepthTest(false, false, gpu::LESS_EQUAL));
|
||||
state->setBlendFunction(true,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
|
|
@ -318,8 +318,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
|||
updateModelBounds();
|
||||
|
||||
// should never fall in here when collision model not fully loaded
|
||||
// hence we assert that all geometries exist and are loaded
|
||||
assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
|
||||
// TODO: assert that all geometries exist and are loaded
|
||||
//assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
|
||||
const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry();
|
||||
|
||||
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
|
||||
|
@ -407,8 +407,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
|||
}
|
||||
shapeInfo.setParams(type, dimensions, getCompoundShapeURL());
|
||||
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
|
||||
// should never fall in here when model not fully loaded
|
||||
assert(_model && _model->isLoaded());
|
||||
// TODO: assert we never fall in here when model not fully loaded
|
||||
//assert(_model && _model->isLoaded());
|
||||
|
||||
updateModelBounds();
|
||||
model->updateGeometry();
|
||||
|
|
|
@ -110,7 +110,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
if (!_geometryID) {
|
||||
_geometryID = geometryCache->allocateID();
|
||||
}
|
||||
geometryCache->bindSimpleProgram(batch, false, transparent, false, false, true);
|
||||
geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false, false);
|
||||
geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID);
|
||||
|
||||
float scale = _lineHeight / _textRenderer->getFontSize();
|
||||
|
|
|
@ -176,8 +176,6 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
}
|
||||
|
||||
//_timer.singleShot
|
||||
|
||||
PerformanceTimer perfTimer("WebEntityRenderer::render");
|
||||
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
||||
|
||||
|
@ -187,11 +185,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
|
|||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio);
|
||||
|
||||
if (fadeRatio < OPAQUE_ALPHA_THRESHOLD) {
|
||||
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, true);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch);
|
||||
}
|
||||
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
|
||||
}
|
||||
|
||||
|
|
|
@ -241,7 +241,6 @@ private:
|
|||
};
|
||||
|
||||
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
||||
qDebug() << "Processing geometry: " << _effectiveBaseURL;
|
||||
_url = _effectiveBaseURL;
|
||||
_textureBaseUrl = _effectiveBaseURL;
|
||||
QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts));
|
||||
|
@ -255,7 +254,6 @@ void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxG
|
|||
QHash<QString, size_t> materialIDAtlas;
|
||||
for (const FBXMaterial& material : _fbxGeometry->materials) {
|
||||
materialIDAtlas[material.materialID] = _materials.size();
|
||||
qDebug() << "setGeometryDefinition() " << _textureBaseUrl;
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
||||
}
|
||||
|
||||
|
@ -348,7 +346,6 @@ Geometry::Geometry(const Geometry& geometry) {
|
|||
|
||||
_materials.reserve(geometry._materials.size());
|
||||
for (const auto& material : geometry._materials) {
|
||||
qDebug() << "Geometry() no base url...";
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(*material));
|
||||
}
|
||||
|
||||
|
@ -434,7 +431,6 @@ void GeometryResource::deleter() {
|
|||
void GeometryResource::setTextures() {
|
||||
if (_fbxGeometry) {
|
||||
for (const FBXMaterial& material : _fbxGeometry->materials) {
|
||||
qDebug() << "setTextures() " << _textureBaseUrl;
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
||||
}
|
||||
}
|
||||
|
@ -536,7 +532,6 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, image
|
|||
NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) :
|
||||
model::Material(*material._material)
|
||||
{
|
||||
qDebug() << "Created network material with base url: " << textureBaseUrl;
|
||||
_textures = Textures(MapChannel::NUM_MAP_CHANNELS);
|
||||
if (!material.albedoTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
|
||||
|
|
|
@ -97,7 +97,10 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky
|
|||
}
|
||||
|
||||
auto skyState = std::make_shared<gpu::State>();
|
||||
skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
// Must match PrepareStencil::STENCIL_BACKGROUND
|
||||
const int8_t STENCIL_BACKGROUND = 0;
|
||||
skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_BACKGROUND, 0xFF, gpu::EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
|
||||
thePipeline = gpu::Pipeline::create(skyShader, skyState);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ ProceduralSkybox::ProceduralSkybox() : model::Skybox() {
|
|||
_procedural._fragmentSource = skybox_frag;
|
||||
// Adjust the pipeline state for background using the stencil test
|
||||
_procedural.setDoesFade(false);
|
||||
_procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
// Must match PrepareStencil::STENCIL_BACKGROUND
|
||||
const int8_t STENCIL_BACKGROUND = 0;
|
||||
_procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_BACKGROUND, 0xFF, gpu::EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
bool ProceduralSkybox::empty() {
|
||||
|
|
|
@ -70,9 +70,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar
|
|||
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
|
||||
PrepareStencil::testMaskNoAA(*state);
|
||||
|
||||
state->setDepthTest(false, false, gpu::LESS_EQUAL);
|
||||
PrepareStencil::testNoAA(*state);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
_antialiasingPipeline = gpu::Pipeline::create(program, state);
|
||||
|
@ -95,7 +94,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() {
|
|||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
|
||||
state->setDepthTest(false, false, gpu::LESS_EQUAL);
|
||||
PrepareStencil::testMaskNoAA(*state);
|
||||
PrepareStencil::testNoAA(*state);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
_blendPipeline = gpu::Pipeline::create(program, state);
|
||||
|
|
|
@ -420,7 +420,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input
|
|||
gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 |
|
||||
gpu::Framebuffer::BUFFER_DEPTH |
|
||||
gpu::Framebuffer::BUFFER_STENCIL,
|
||||
vec4(vec3(0), 0), 1.0, 1, true);
|
||||
vec4(vec3(0), 0), 1.0, 0, true);
|
||||
|
||||
// For the rest of the rendering, bind the lighting model
|
||||
batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer());
|
||||
|
|
|
@ -561,7 +561,7 @@ void GeometryCache::initializeShapePipelines() {
|
|||
render::ShapePipelinePointer GeometryCache::getShapePipeline(bool textured, bool transparent, bool culled,
|
||||
bool unlit, bool depthBias) {
|
||||
|
||||
return std::make_shared<render::ShapePipeline>(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false), nullptr,
|
||||
return std::make_shared<render::ShapePipeline>(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false, true), nullptr,
|
||||
[](const render::ShapePipeline& , gpu::Batch& batch, render::Args*) {
|
||||
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
}
|
||||
|
@ -1877,6 +1877,7 @@ public:
|
|||
IS_UNLIT_FLAG,
|
||||
HAS_DEPTH_BIAS_FLAG,
|
||||
IS_FADING_FLAG,
|
||||
IS_ANTIALIASED_FLAG,
|
||||
|
||||
NUM_FLAGS,
|
||||
};
|
||||
|
@ -1888,6 +1889,7 @@ public:
|
|||
IS_UNLIT = (1 << IS_UNLIT_FLAG),
|
||||
HAS_DEPTH_BIAS = (1 << HAS_DEPTH_BIAS_FLAG),
|
||||
IS_FADING = (1 << IS_FADING_FLAG),
|
||||
IS_ANTIALIASED = (1 << IS_ANTIALIASED_FLAG),
|
||||
};
|
||||
typedef unsigned short Flags;
|
||||
|
||||
|
@ -1899,6 +1901,7 @@ public:
|
|||
bool isUnlit() const { return isFlag(IS_UNLIT); }
|
||||
bool hasDepthBias() const { return isFlag(HAS_DEPTH_BIAS); }
|
||||
bool isFading() const { return isFlag(IS_FADING); }
|
||||
bool isAntiAliased() const { return isFlag(IS_ANTIALIASED); }
|
||||
|
||||
Flags _flags = 0;
|
||||
short _spare = 0;
|
||||
|
@ -1907,9 +1910,9 @@ public:
|
|||
|
||||
|
||||
SimpleProgramKey(bool textured = false, bool transparent = false, bool culled = true,
|
||||
bool unlit = false, bool depthBias = false, bool fading = false) {
|
||||
bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true) {
|
||||
_flags = (textured ? IS_TEXTURED : 0) | (transparent ? IS_TRANSPARENT : 0) | (culled ? IS_CULLED : 0) |
|
||||
(unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0) | (fading ? IS_FADING : 0);
|
||||
(unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0) | (fading ? IS_FADING : 0) | (isAntiAliased ? IS_ANTIALIASED : 0);
|
||||
}
|
||||
|
||||
SimpleProgramKey(int bitmask) : _flags(bitmask) {}
|
||||
|
@ -1958,8 +1961,8 @@ gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent) {
|
|||
return transparent ? _simpleTransparentWebBrowserPipelineNoAA : _simpleOpaqueWebBrowserPipelineNoAA;
|
||||
}
|
||||
|
||||
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) {
|
||||
batch.setPipeline(getSimplePipeline(textured, transparent, culled, unlit, depthBiased));
|
||||
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool isAntiAliased) {
|
||||
batch.setPipeline(getSimplePipeline(textured, transparent, culled, unlit, depthBiased, false, isAntiAliased));
|
||||
|
||||
// If not textured, set a default albedo map
|
||||
if (!textured) {
|
||||
|
@ -1968,8 +1971,8 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool tra
|
|||
}
|
||||
}
|
||||
|
||||
gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool fading) {
|
||||
SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased, fading };
|
||||
gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool fading, bool isAntiAliased) {
|
||||
SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased, fading, isAntiAliased };
|
||||
|
||||
// If the pipeline already exists, return it
|
||||
auto it = _simplePrograms.find(config);
|
||||
|
@ -2027,11 +2030,10 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp
|
|||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
||||
if (config.isTransparent()) {
|
||||
PrepareStencil::testMask(*state);
|
||||
}
|
||||
else {
|
||||
PrepareStencil::testMaskDrawShape(*state);
|
||||
if (config.isAntiAliased()) {
|
||||
config.isTransparent() ? PrepareStencil::testMask(*state) : PrepareStencil::testMaskDrawShape(*state);
|
||||
} else {
|
||||
PrepareStencil::testMaskDrawShapeNoAA(*state);
|
||||
}
|
||||
|
||||
gpu::ShaderPointer program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : (config.isFading() ? _simpleFadeShader : _simpleShader);
|
||||
|
|
|
@ -161,10 +161,10 @@ public:
|
|||
|
||||
// Bind the pipeline and get the state to render static geometry
|
||||
void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool transparent = false, bool culled = true,
|
||||
bool unlit = false, bool depthBias = false);
|
||||
bool unlit = false, bool depthBias = false, bool isAntiAliased = true);
|
||||
// Get the pipeline to render static geometry
|
||||
static gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true,
|
||||
bool unlit = false, bool depthBias = false, bool fading = false);
|
||||
bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true);
|
||||
|
||||
void bindWebBrowserProgram(gpu::Batch& batch, bool transparent = false);
|
||||
gpu::PipelinePointer getWebBrowserProgram(bool transparent);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <PathUtils.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <gpu/Context.h>
|
||||
#include "StencilMaskPass.h"
|
||||
|
||||
#include "FramebufferCache.h"
|
||||
#include "TextureCache.h"
|
||||
|
@ -93,7 +94,7 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext,
|
|||
gpu::Framebuffer::BUFFER_COLOR0 |
|
||||
gpu::Framebuffer::BUFFER_DEPTH |
|
||||
gpu::Framebuffer::BUFFER_STENCIL,
|
||||
vec4(vec3(0), 1), 1.0, 0.0, true);
|
||||
vec4(vec3(0), 1), 1.0, 0, true);
|
||||
});
|
||||
|
||||
framebuffer = _framebuffer;
|
||||
|
@ -130,11 +131,7 @@ const gpu::PipelinePointer Stencil::getPipeline() {
|
|||
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setDepthTest(true, false, gpu::LESS_EQUAL);
|
||||
const gpu::int8 STENCIL_OPAQUE = 1;
|
||||
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_OPAQUE, 0xFF, gpu::ALWAYS,
|
||||
gpu::State::STENCIL_OP_REPLACE,
|
||||
gpu::State::STENCIL_OP_REPLACE,
|
||||
gpu::State::STENCIL_OP_KEEP));
|
||||
PrepareStencil::drawBackground(*state);
|
||||
|
||||
_stencilPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
|
|
@ -104,30 +104,51 @@ void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::F
|
|||
});
|
||||
}
|
||||
|
||||
// Always draw MASK to the stencil buffer (used to always prevent drawing in certain areas later)
|
||||
void PrepareStencil::drawMask(gpu::State& state) {
|
||||
state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE));
|
||||
state.setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_MASK, 0xFF, gpu::ALWAYS,
|
||||
gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE));
|
||||
}
|
||||
|
||||
// Draw BACKGROUND to the stencil buffer behind everything else
|
||||
void PrepareStencil::drawBackground(gpu::State& state) {
|
||||
state.setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_BACKGROUND, 0xFF, gpu::ALWAYS,
|
||||
gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
// Pass if this area has NOT been marked as MASK
|
||||
void PrepareStencil::testMask(gpu::State& state) {
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_MASK, 0xFF, gpu::NOT_EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
void PrepareStencil::testMaskNoAA(gpu::State& state) {
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK | PrepareStencil::STENCIL_NO_AA, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
// Pass if this area has NOT been marked as NO_AA or anything containing NO_AA
|
||||
void PrepareStencil::testNoAA(gpu::State& state) {
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_NO_AA, STENCIL_NO_AA, gpu::NOT_EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
// Pass if this area WAS marked as BACKGROUND
|
||||
// (see: model/src/Skybox.cpp, procedural/src/ProceduralSkybox.cpp)
|
||||
void PrepareStencil::testBackground(gpu::State& state) {
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_BACKGROUND, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
void PrepareStencil::testMaskDrawShape(gpu::State& state) {
|
||||
state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO));
|
||||
}
|
||||
|
||||
void PrepareStencil::testMaskDrawShapeNoAA(gpu::State& state) {
|
||||
state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK | PrepareStencil::STENCIL_NO_AA, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE));
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_BACKGROUND, 0xFF, gpu::EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
// Pass if this area WAS marked as SHAPE or anything containing SHAPE
|
||||
void PrepareStencil::testShape(gpu::State& state) {
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_SHAPE, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_SHAPE, STENCIL_SHAPE, gpu::EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
}
|
||||
|
||||
// Pass if this area was NOT marked as MASK, write to SHAPE if it passes
|
||||
void PrepareStencil::testMaskDrawShape(gpu::State& state) {
|
||||
state.setStencilTest(true, STENCIL_SHAPE, gpu::State::StencilTest(STENCIL_MASK | STENCIL_SHAPE, STENCIL_MASK, gpu::NOT_EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE));
|
||||
}
|
||||
|
||||
// Pass if this area was NOT marked as MASK, write to SHAPE and NO_AA if it passes
|
||||
void PrepareStencil::testMaskDrawShapeNoAA(gpu::State& state) {
|
||||
state.setStencilTest(true, STENCIL_SHAPE | STENCIL_NO_AA, gpu::State::StencilTest(STENCIL_MASK | STENCIL_SHAPE | STENCIL_NO_AA, STENCIL_MASK, gpu::NOT_EQUAL,
|
||||
gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE));
|
||||
}
|
|
@ -41,20 +41,20 @@ public:
|
|||
|
||||
void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& dstFramebuffer);
|
||||
|
||||
static const gpu::int8 STENCIL_SHAPE = 0;
|
||||
static const gpu::int8 STENCIL_BACKGROUND = 1 << 0;
|
||||
static const gpu::int8 STENCIL_MASK = 1 << 1;
|
||||
static const gpu::int8 STENCIL_NO_AA = 1 << 2;
|
||||
|
||||
// Always use 0 to clear the stencil buffer to set it to background
|
||||
static const gpu::int8 STENCIL_BACKGROUND = 0; // must match values in Skybox.cpp and ProceduralSkybox.cpp
|
||||
static const gpu::int8 STENCIL_MASK = 1 << 0;
|
||||
static const gpu::int8 STENCIL_SHAPE = 1 << 1;
|
||||
static const gpu::int8 STENCIL_NO_AA = 1 << 2;
|
||||
|
||||
static void drawMask(gpu::State& state);
|
||||
static void drawBackground(gpu::State& state);
|
||||
static void testNoAA(gpu::State& state);
|
||||
static void testMask(gpu::State& state);
|
||||
static void testMaskNoAA(gpu::State& state);
|
||||
static void testBackground(gpu::State& state);
|
||||
static void testShape(gpu::State& state);
|
||||
static void testMaskDrawShape(gpu::State& state);
|
||||
static void testMaskDrawShapeNoAA(gpu::State& state);
|
||||
static void testShape(gpu::State& state);
|
||||
|
||||
|
||||
private:
|
||||
gpu::PipelinePointer _meshStencilPipeline;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.frag
|
||||
// sdf_text3D.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2015-02-04
|
||||
|
@ -38,18 +38,21 @@ void main() {
|
|||
// perform adaptive anti-aliasing of the edges
|
||||
// The larger we're rendering, the less anti-aliasing we need
|
||||
float s = smoothing * length(fwidth(_texCoord0));
|
||||
float w = clamp( s, 0.0, 0.5);
|
||||
float w = clamp(s, 0.0, 0.5);
|
||||
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
|
||||
|
||||
// discard if unvisible
|
||||
// discard if invisible
|
||||
if (a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
packDeferredFragmentTranslucent(
|
||||
|
||||
packDeferredFragment(
|
||||
normalize(_normal),
|
||||
a * Color.a,
|
||||
Color.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_ROUGHNESS);
|
||||
DEFAULT_ROUGHNESS,
|
||||
DEFAULT_METALLIC,
|
||||
DEFAULT_EMISSIVE,
|
||||
DEFAULT_OCCLUSION,
|
||||
DEFAULT_SCATTERING);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.vert
|
||||
// sdf_text3D.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Brad Davis on 10/14/13.
|
||||
|
@ -27,5 +27,6 @@ void main() {
|
|||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal.xyz)$>
|
||||
const vec3 normal = vec3(0, 0, 1);
|
||||
<$transformModelToWorldDir(cam, obj, normal, _normal)$>
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.frag
|
||||
// sdf_text3D_transparent.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2015-02-04
|
||||
|
@ -10,6 +10,8 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
<@include DeferredBufferWrite.slh@>
|
||||
|
||||
uniform sampler2D Font;
|
||||
uniform bool Outline;
|
||||
uniform vec4 Color;
|
||||
|
@ -18,8 +20,6 @@ uniform vec4 Color;
|
|||
in vec3 _normal;
|
||||
in vec2 _texCoord0;
|
||||
|
||||
layout(location = 0) out vec4 _fragColor0;
|
||||
|
||||
const float gamma = 2.2;
|
||||
const float smoothing = 32.0;
|
||||
const float interiorCutoff = 0.8;
|
||||
|
@ -38,15 +38,18 @@ void main() {
|
|||
// perform adaptive anti-aliasing of the edges
|
||||
// The larger we're rendering, the less anti-aliasing we need
|
||||
float s = smoothing * length(fwidth(_texCoord0));
|
||||
float w = clamp( s, 0.0, 0.5);
|
||||
float w = clamp(s, 0.0, 0.5);
|
||||
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
|
||||
|
||||
// gamma correction for linear attenuation
|
||||
a = pow(a, 1.0 / gamma);
|
||||
|
||||
// discard if unvisible
|
||||
// discard if invisible
|
||||
if (a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
_fragColor0 = vec4(Color.rgb, a);
|
||||
|
||||
packDeferredFragmentTranslucent(
|
||||
normalize(_normal),
|
||||
a * Color.a,
|
||||
Color.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_ROUGHNESS);
|
||||
}
|
|
@ -75,7 +75,7 @@ void main(void) {
|
|||
max(0, 1.0 - shininess / 128.0),
|
||||
DEFAULT_METALLIC,
|
||||
specular,
|
||||
specular);
|
||||
vec3(clamp(emissiveAmount, 0.0, 1.0)));
|
||||
} else {
|
||||
packDeferredFragment(
|
||||
normal,
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
|
||||
#include "sdf_text3D_vert.h"
|
||||
#include "sdf_text3D_frag.h"
|
||||
#include "sdf_text3D_overlay_frag.h"
|
||||
#include "sdf_text3D_transparent_frag.h"
|
||||
|
||||
#include "../RenderUtilsLogging.h"
|
||||
#include "FontFamilies.h"
|
||||
#include "../StencilMaskPass.h"
|
||||
|
||||
static std::mutex fontMutex;
|
||||
|
||||
|
@ -224,13 +225,13 @@ void Font::setupGPU() {
|
|||
{
|
||||
auto vertexShader = gpu::Shader::createVertex(std::string(sdf_text3D_vert));
|
||||
auto pixelShader = gpu::Shader::createPixel(std::string(sdf_text3D_frag));
|
||||
auto pixelShaderOverlay = gpu::Shader::createPixel(std::string(sdf_text3D_overlay_frag));
|
||||
auto pixelShaderTransparent = gpu::Shader::createPixel(std::string(sdf_text3D_transparent_frag));
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader);
|
||||
gpu::ShaderPointer programOverlay = gpu::Shader::createProgram(vertexShader, pixelShaderOverlay);
|
||||
gpu::ShaderPointer programTransparent = gpu::Shader::createProgram(vertexShader, pixelShaderTransparent);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
gpu::Shader::makeProgram(*programOverlay, slotBindings);
|
||||
gpu::Shader::makeProgram(*programTransparent, slotBindings);
|
||||
|
||||
_fontLoc = program->getTextures().findLocation("Font");
|
||||
_outlineLoc = program->getUniforms().findLocation("Outline");
|
||||
|
@ -239,15 +240,20 @@ void Font::setupGPU() {
|
|||
auto state = std::make_shared<gpu::State>();
|
||||
state->setCullMode(gpu::State::CULL_BACK);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(true,
|
||||
state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
PrepareStencil::testMaskDrawShapeNoAA(*state);
|
||||
_pipeline = gpu::Pipeline::create(program, state);
|
||||
|
||||
auto layeredState = std::make_shared<gpu::State>();
|
||||
layeredState->setCullMode(gpu::State::CULL_BACK);
|
||||
layeredState->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
_layeredPipeline = gpu::Pipeline::create(programOverlay, layeredState);
|
||||
auto transparentState = std::make_shared<gpu::State>();
|
||||
transparentState->setCullMode(gpu::State::CULL_BACK);
|
||||
transparentState->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
transparentState->setBlendFunction(true,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
PrepareStencil::testMaskDrawShapeNoAA(*transparentState);
|
||||
_transparentPipeline = gpu::Pipeline::create(programTransparent, transparentState);
|
||||
}
|
||||
|
||||
// Sanity checks
|
||||
|
@ -361,7 +367,7 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c
|
|||
|
||||
setupGPU();
|
||||
|
||||
batch.setPipeline(layered ? _layeredPipeline : _pipeline);
|
||||
batch.setPipeline(((*color).a < 1.0f || layered) ? _transparentPipeline : _pipeline);
|
||||
batch.setResourceTexture(_fontLoc, _texture);
|
||||
batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT));
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ private:
|
|||
|
||||
// gpu structures
|
||||
gpu::PipelinePointer _pipeline;
|
||||
gpu::PipelinePointer _layeredPipeline;
|
||||
gpu::PipelinePointer _transparentPipeline;
|
||||
gpu::TexturePointer _texture;
|
||||
gpu::Stream::FormatPointer _format;
|
||||
gpu::BufferPointer _verticesBuffer;
|
||||
|
|
|
@ -473,6 +473,14 @@ glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat&
|
|||
glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
|
||||
}
|
||||
|
||||
glm::mat4 createMatFromScale(const glm::vec3& scale) {
|
||||
glm::vec3 xAxis = glm::vec3(scale.x, 0.0f, 0.0f);
|
||||
glm::vec3 yAxis = glm::vec3(0.0f, scale.y, 0.0f);
|
||||
glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, scale.z);
|
||||
return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
|
||||
glm::vec4(zAxis, 0.0f), glm::vec4(Vectors::ZERO, 1.0f));
|
||||
}
|
||||
|
||||
// cancel out roll
|
||||
glm::quat cancelOutRoll(const glm::quat& q) {
|
||||
glm::vec3 forward = q * Vectors::FRONT;
|
||||
|
|
|
@ -231,6 +231,7 @@ glm::tvec4<T, P> lerp(const glm::tvec4<T, P>& x, const glm::tvec4<T, P>& y, T a)
|
|||
|
||||
glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p);
|
||||
glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans);
|
||||
glm::mat4 createMatFromScale(const glm::vec3& scale);
|
||||
glm::quat cancelOutRoll(const glm::quat& q);
|
||||
glm::quat cancelOutRollAndPitch(const glm::quat& q);
|
||||
glm::mat4 cancelOutRollAndPitch(const glm::mat4& m);
|
||||
|
|
|
@ -77,13 +77,13 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve
|
|||
normal.setProperty("x", event._normal.x);
|
||||
normal.setProperty("y", event._normal.y);
|
||||
normal.setProperty("z", event._normal.z);
|
||||
obj.setProperty("pos3D", normal);
|
||||
obj.setProperty("normal", normal);
|
||||
|
||||
QScriptValue direction = engine->newObject();
|
||||
direction.setProperty("x", event._direction.x);
|
||||
direction.setProperty("y", event._direction.y);
|
||||
direction.setProperty("z", event._direction.z);
|
||||
obj.setProperty("pos3D", direction);
|
||||
obj.setProperty("direction", direction);
|
||||
|
||||
bool isPrimaryButton = false;
|
||||
bool isSecondaryButton = false;
|
||||
|
|
|
@ -762,6 +762,8 @@ QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResu
|
|||
QScriptValue intersection = vec3toScriptValue(engine, rayPickResult.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
obj.setProperty("intersects", rayPickResult.type != NONE);
|
||||
QScriptValue searchRay = pickRayToScriptValue(engine, rayPickResult.searchRay);
|
||||
obj.setProperty("searchRay", searchRay);
|
||||
QScriptValue surfaceNormal = vec3toScriptValue(engine, rayPickResult.surfaceNormal);
|
||||
obj.setProperty("surfaceNormal", surfaceNormal);
|
||||
return obj;
|
||||
|
|
|
@ -137,7 +137,7 @@ QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay)
|
|||
void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay);
|
||||
|
||||
enum IntersectionType {
|
||||
NONE,
|
||||
NONE = 0,
|
||||
ENTITY,
|
||||
OVERLAY,
|
||||
AVATAR,
|
||||
|
@ -147,12 +147,14 @@ enum IntersectionType {
|
|||
class RayPickResult {
|
||||
public:
|
||||
RayPickResult() {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, const float distance, const glm::vec3& intersection, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
type(type), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {}
|
||||
RayPickResult(const PickRay& searchRay) : searchRay(searchRay) {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, const float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
type(type), objectID(objectID), distance(distance), intersection(intersection), searchRay(searchRay), surfaceNormal(surfaceNormal) {}
|
||||
IntersectionType type { NONE };
|
||||
QUuid objectID { 0 };
|
||||
QUuid objectID;
|
||||
float distance { FLT_MAX };
|
||||
glm::vec3 intersection { NAN };
|
||||
PickRay searchRay;
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
};
|
||||
Q_DECLARE_METATYPE(RayPickResult)
|
||||
|
|
|
@ -136,7 +136,7 @@ void EyeTracker::onStreamStarted() {
|
|||
qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result);
|
||||
// Display error dialog unless SMI SDK has already displayed an error message.
|
||||
if (result != SMI_ERROR_HMD_NOT_SUPPORTED) {
|
||||
OffscreenUi::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
|
||||
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Eye Tracker: Started streaming";
|
||||
|
@ -149,7 +149,7 @@ void EyeTracker::onStreamStarted() {
|
|||
result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION);
|
||||
if (result != SMI_RET_SUCCESS) {
|
||||
qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result);
|
||||
OffscreenUi::warning(nullptr, "Eye Tracker Error", "Error loading calibration"
|
||||
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", "Error loading calibration"
|
||||
+ smiReturnValueToString(result));
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration";
|
||||
|
@ -165,7 +165,7 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) {
|
|||
int result = smi_setCallback(eyeTrackerCallback);
|
||||
if (result != SMI_RET_SUCCESS) {
|
||||
qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result);
|
||||
OffscreenUi::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
|
||||
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
|
||||
} else {
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ void EyeTracker::calibrate(int points) {
|
|||
}
|
||||
|
||||
if (result != SMI_RET_SUCCESS) {
|
||||
OffscreenUi::warning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result));
|
||||
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -91,6 +91,13 @@ QObject* OffscreenUi::getFlags() {
|
|||
return offscreenFlags;
|
||||
}
|
||||
|
||||
void OffscreenUi::removeModalDialog(QObject* modal) {
|
||||
if (modal) {
|
||||
_modalDialogListeners.removeOne(modal);
|
||||
modal->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void OffscreenUi::create() {
|
||||
OffscreenQmlSurface::create();
|
||||
auto myContext = getSurfaceContext();
|
||||
|
@ -145,45 +152,6 @@ bool OffscreenUi::isVisible(const QString& name) {
|
|||
}
|
||||
}
|
||||
|
||||
class ModalDialogListener : public QObject {
|
||||
Q_OBJECT
|
||||
friend class OffscreenUi;
|
||||
|
||||
protected:
|
||||
ModalDialogListener(QQuickItem* dialog) : _dialog(dialog) {
|
||||
if (!dialog) {
|
||||
_finished = true;
|
||||
return;
|
||||
}
|
||||
connect(_dialog, SIGNAL(destroyed()), this, SLOT(onDestroyed()));
|
||||
}
|
||||
|
||||
~ModalDialogListener() {
|
||||
if (_dialog) {
|
||||
disconnect(_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
virtual QVariant waitForResult() {
|
||||
while (!_finished) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
|
||||
protected slots:
|
||||
void onDestroyed() {
|
||||
_finished = true;
|
||||
disconnect(_dialog);
|
||||
_dialog = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
QQuickItem* _dialog;
|
||||
bool _finished { false };
|
||||
QVariant _result;
|
||||
};
|
||||
|
||||
class MessageBoxListener : public ModalDialogListener {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -204,6 +172,9 @@ private slots:
|
|||
void onSelected(int button) {
|
||||
_result = button;
|
||||
_finished = true;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
emit response(_result);
|
||||
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
|
||||
disconnect(_dialog);
|
||||
}
|
||||
};
|
||||
|
@ -263,6 +234,25 @@ QMessageBox::StandardButton OffscreenUi::messageBox(Icon icon, const QString& ti
|
|||
return static_cast<QMessageBox::StandardButton>(waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton)));
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::asyncMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "asyncMessageBox",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(Icon, icon),
|
||||
Q_ARG(QString, title),
|
||||
Q_ARG(QString, text),
|
||||
Q_ARG(QMessageBox::StandardButtons, buttons),
|
||||
Q_ARG(QMessageBox::StandardButton, defaultButton));
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageBoxListener* messageBoxListener = new MessageBoxListener(createMessageBox(icon, title, text, buttons, defaultButton));
|
||||
QObject* modalDialog = qobject_cast<QObject*>(messageBoxListener);
|
||||
_modalDialogListeners.push_back(modalDialog);
|
||||
return messageBoxListener;
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton OffscreenUi::critical(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_CRITICAL, title, text, buttons, defaultButton);
|
||||
|
@ -271,15 +261,36 @@ QMessageBox::StandardButton OffscreenUi::information(const QString& title, const
|
|||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_INFORMATION, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::asyncCritical(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_CRITICAL, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::asyncInformation(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_INFORMATION, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton OffscreenUi::question(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_QUESTION, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
ModalDialogListener *OffscreenUi::asyncQuestion(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_QUESTION, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton OffscreenUi::warning(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_WARNING, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::asyncWarning(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
|
||||
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_WARNING, title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
|
||||
class InputDialogListener : public ModalDialogListener {
|
||||
|
@ -296,6 +307,9 @@ class InputDialogListener : public ModalDialogListener {
|
|||
private slots:
|
||||
void onSelected(const QVariant& result) {
|
||||
_result = result;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
emit response(_result);
|
||||
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
|
||||
_finished = true;
|
||||
disconnect(_dialog);
|
||||
}
|
||||
|
@ -349,6 +363,31 @@ QVariant OffscreenUi::getCustomInfo(const Icon icon, const QString& title, const
|
|||
return result;
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getTextAsync(const Icon icon, const QString& title, const QString& label, const QString& text) {
|
||||
return DependencyManager::get<OffscreenUi>()->inputDialogAsync(icon, title, label, text);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getItemAsync(const Icon icon, const QString& title, const QString& label, const QStringList& items,
|
||||
int current, bool editable) {
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto inputDialog = offscreenUi->createInputDialog(icon, title, label, current);
|
||||
if (!inputDialog) {
|
||||
return nullptr;
|
||||
}
|
||||
inputDialog->setProperty("items", items);
|
||||
inputDialog->setProperty("editable", editable);
|
||||
|
||||
InputDialogListener* inputDialogListener = new InputDialogListener(inputDialog);
|
||||
offscreenUi->getModalDialogListeners().push_back(qobject_cast<QObject*>(inputDialogListener));
|
||||
|
||||
return inputDialogListener;
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getCustomInfoAsync(const Icon icon, const QString& title, const QVariantMap& config) {
|
||||
return DependencyManager::get<OffscreenUi>()->customInputDialogAsync(icon, title, config);
|
||||
}
|
||||
|
||||
QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariant result;
|
||||
|
@ -364,6 +403,24 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q
|
|||
return waitForInputDialogResult(createInputDialog(icon, title, label, current));
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::inputDialogAsync(const Icon icon, const QString& title, const QString& label, const QVariant& current) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "inputDialogAsync",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(Icon, icon),
|
||||
Q_ARG(QString, title),
|
||||
Q_ARG(QString, label),
|
||||
Q_ARG(QVariant, current));
|
||||
return ret;
|
||||
}
|
||||
|
||||
InputDialogListener* inputDialogListener = new InputDialogListener(createInputDialog(icon, title, label, current));
|
||||
QObject* inputDialog = qobject_cast<QObject*>(inputDialogListener);
|
||||
_modalDialogListeners.push_back(inputDialog);
|
||||
return inputDialogListener;
|
||||
}
|
||||
|
||||
QVariant OffscreenUi::customInputDialog(const Icon icon, const QString& title, const QVariantMap& config) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariant result;
|
||||
|
@ -384,6 +441,23 @@ QVariant OffscreenUi::customInputDialog(const Icon icon, const QString& title, c
|
|||
return result;
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::customInputDialogAsync(const Icon icon, const QString& title, const QVariantMap& config) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "customInputDialogAsync",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(Icon, icon),
|
||||
Q_ARG(QString, title),
|
||||
Q_ARG(QVariantMap, config));
|
||||
return ret;
|
||||
}
|
||||
|
||||
InputDialogListener* inputDialogListener = new InputDialogListener(createCustomInputDialog(icon, title, config));
|
||||
QObject* inputDialog = qobject_cast<QObject*>(inputDialogListener);
|
||||
_modalDialogListeners.push_back(inputDialog);
|
||||
return inputDialogListener;
|
||||
}
|
||||
|
||||
void OffscreenUi::togglePinned() {
|
||||
bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned");
|
||||
if (!invokeResult) {
|
||||
|
@ -555,6 +629,7 @@ void OffscreenUi::createDesktop(const QUrl& url) {
|
|||
#endif
|
||||
|
||||
load(url, [=](QQmlContext* context, QObject* newObject) {
|
||||
Q_UNUSED(context)
|
||||
_desktop = static_cast<QQuickItem*>(newObject);
|
||||
getSurfaceContext()->setContextProperty("desktop", _desktop);
|
||||
_toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
|
||||
|
@ -600,8 +675,11 @@ class FileDialogListener : public ModalDialogListener {
|
|||
|
||||
private slots:
|
||||
void onSelectedFile(QVariant file) {
|
||||
_result = file;
|
||||
_result = file.toUrl().toLocalFile();
|
||||
_finished = true;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
emit response(_result);
|
||||
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
|
||||
disconnect(_dialog);
|
||||
}
|
||||
};
|
||||
|
@ -637,6 +715,35 @@ QString OffscreenUi::fileDialog(const QVariantMap& properties) {
|
|||
return result.toUrl().toLocalFile();
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::fileDialogAsync(const QVariantMap& properties) {
|
||||
QVariant buildDialogResult;
|
||||
bool invokeResult;
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
if (tablet->getToolbarMode()) {
|
||||
invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
|
||||
Q_RETURN_ARG(QVariant, buildDialogResult),
|
||||
Q_ARG(QVariant, QVariant::fromValue(properties)));
|
||||
} else {
|
||||
QQuickItem* tabletRoot = tablet->getTabletRoot();
|
||||
invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog",
|
||||
Q_RETURN_ARG(QVariant, buildDialogResult),
|
||||
Q_ARG(QVariant, QVariant::fromValue(properties)));
|
||||
emit tabletScriptingInterface->tabletNotification();
|
||||
}
|
||||
|
||||
if (!invokeResult) {
|
||||
qWarning() << "Failed to create file open dialog";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileDialogListener* fileDialogListener = new FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult));
|
||||
QObject* fileModalDialog = qobject_cast<QObject*>(fileDialogListener);
|
||||
_modalDialogListeners.push_back(fileModalDialog);
|
||||
|
||||
return fileDialogListener;
|
||||
}
|
||||
|
||||
QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QString result;
|
||||
|
@ -659,6 +766,28 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir,
|
|||
return fileDialog(map);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::fileOpenDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "fileOpenDialogAsync",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(QString, caption),
|
||||
Q_ARG(QString, dir),
|
||||
Q_ARG(QString, filter),
|
||||
Q_ARG(QString*, selectedFilter),
|
||||
Q_ARG(QFileDialog::Options, options));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// FIXME support returning the selected filter... somehow?
|
||||
QVariantMap map;
|
||||
map.insert("caption", caption);
|
||||
map.insert("dir", QUrl::fromLocalFile(dir));
|
||||
map.insert("filter", filter);
|
||||
map.insert("options", static_cast<int>(options));
|
||||
return fileDialogAsync(map);
|
||||
}
|
||||
|
||||
QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QString result;
|
||||
|
@ -683,6 +812,30 @@ QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir,
|
|||
return fileDialog(map);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::fileSaveDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "fileSaveDialogAsync",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(QString, caption),
|
||||
Q_ARG(QString, dir),
|
||||
Q_ARG(QString, filter),
|
||||
Q_ARG(QString*, selectedFilter),
|
||||
Q_ARG(QFileDialog::Options, options));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// FIXME support returning the selected filter... somehow?
|
||||
QVariantMap map;
|
||||
map.insert("caption", caption);
|
||||
map.insert("dir", QUrl::fromLocalFile(dir));
|
||||
map.insert("filter", filter);
|
||||
map.insert("options", static_cast<int>(options));
|
||||
map.insert("saveDialog", true);
|
||||
|
||||
return fileDialogAsync(map);
|
||||
}
|
||||
|
||||
QString OffscreenUi::existingDirectoryDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QString result;
|
||||
|
@ -705,18 +858,58 @@ QString OffscreenUi::existingDirectoryDialog(const QString& caption, const QStri
|
|||
return fileDialog(map);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::existingDirectoryDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "existingDirectoryDialogAsync",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(QString, caption),
|
||||
Q_ARG(QString, dir),
|
||||
Q_ARG(QString, filter),
|
||||
Q_ARG(QString*, selectedFilter),
|
||||
Q_ARG(QFileDialog::Options, options));
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVariantMap map;
|
||||
map.insert("caption", caption);
|
||||
map.insert("dir", QUrl::fromLocalFile(dir));
|
||||
map.insert("filter", filter);
|
||||
map.insert("options", static_cast<int>(options));
|
||||
map.insert("selectDirectory", true);
|
||||
return fileDialogAsync(map);
|
||||
}
|
||||
|
||||
QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->fileOpenDialog(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getOpenFileNameAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->fileOpenDialogAsync(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
QString OffscreenUi::getSaveFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->fileSaveDialog(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getSaveFileNameAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->fileSaveDialogAsync(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
QString OffscreenUi::getExistingDirectory(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->existingDirectoryDialog(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getExistingDirectoryAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->existingDirectoryDialogAsync(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
class AssetDialogListener : public ModalDialogListener {
|
||||
// ATP equivalent of FileDialogListener.
|
||||
Q_OBJECT
|
||||
|
@ -732,6 +925,9 @@ class AssetDialogListener : public ModalDialogListener {
|
|||
private slots:
|
||||
void onSelectedAsset(QVariant asset) {
|
||||
_result = asset;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
emit response(_result);
|
||||
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
|
||||
_finished = true;
|
||||
disconnect(_dialog);
|
||||
}
|
||||
|
@ -769,6 +965,39 @@ QString OffscreenUi::assetDialog(const QVariantMap& properties) {
|
|||
return result.toUrl().toString();
|
||||
}
|
||||
|
||||
ModalDialogListener *OffscreenUi::assetDialogAsync(const QVariantMap& properties) {
|
||||
// ATP equivalent of fileDialog().
|
||||
QVariant buildDialogResult;
|
||||
bool invokeResult;
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
if (tablet->getToolbarMode()) {
|
||||
invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog",
|
||||
Q_RETURN_ARG(QVariant, buildDialogResult),
|
||||
Q_ARG(QVariant, QVariant::fromValue(properties)));
|
||||
} else {
|
||||
QQuickItem* tabletRoot = tablet->getTabletRoot();
|
||||
invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog",
|
||||
Q_RETURN_ARG(QVariant, buildDialogResult),
|
||||
Q_ARG(QVariant, QVariant::fromValue(properties)));
|
||||
emit tabletScriptingInterface->tabletNotification();
|
||||
}
|
||||
|
||||
if (!invokeResult) {
|
||||
qWarning() << "Failed to create asset open dialog";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AssetDialogListener* assetDialogListener = new AssetDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult));
|
||||
QObject* assetModalDialog = qobject_cast<QObject*>(assetDialogListener);
|
||||
_modalDialogListeners.push_back(assetModalDialog);
|
||||
return assetDialogListener;
|
||||
}
|
||||
|
||||
QList<QObject *> &OffscreenUi::getModalDialogListeners() {
|
||||
return _modalDialogListeners;
|
||||
}
|
||||
|
||||
QString OffscreenUi::assetOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
// ATP equivalent of fileOpenDialog().
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
@ -792,11 +1021,41 @@ QString OffscreenUi::assetOpenDialog(const QString& caption, const QString& dir,
|
|||
return assetDialog(map);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::assetOpenDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
|
||||
// ATP equivalent of fileOpenDialog().
|
||||
if (QThread::currentThread() != thread()) {
|
||||
ModalDialogListener* ret;
|
||||
BLOCKING_INVOKE_METHOD(this, "assetOpenDialogAsync",
|
||||
Q_RETURN_ARG(ModalDialogListener*, ret),
|
||||
Q_ARG(QString, caption),
|
||||
Q_ARG(QString, dir),
|
||||
Q_ARG(QString, filter),
|
||||
Q_ARG(QString*, selectedFilter),
|
||||
Q_ARG(QFileDialog::Options, options));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// FIXME support returning the selected filter... somehow?
|
||||
QVariantMap map;
|
||||
map.insert("caption", caption);
|
||||
map.insert("dir", dir);
|
||||
map.insert("filter", filter);
|
||||
map.insert("options", static_cast<int>(options));
|
||||
return assetDialogAsync(map);
|
||||
}
|
||||
|
||||
QString OffscreenUi::getOpenAssetName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
// ATP equivalent of getOpenFileName().
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->assetOpenDialog(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
ModalDialogListener* OffscreenUi::getOpenAssetNameAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
|
||||
// ATP equivalent of getOpenFileName().
|
||||
Q_UNUSED(ignored)
|
||||
return DependencyManager::get<OffscreenUi>()->assetOpenDialogAsync(caption, dir, filter, selectedFilter, options);
|
||||
}
|
||||
|
||||
bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
||||
if (!filterEnabled(originalDestination, event)) {
|
||||
return false;
|
||||
|
@ -836,5 +1095,31 @@ unsigned int OffscreenUi::getMenuUserDataId() const {
|
|||
return _vrMenu->_userDataId;
|
||||
}
|
||||
|
||||
#include "OffscreenUi.moc"
|
||||
ModalDialogListener::ModalDialogListener(QQuickItem *dialog) : _dialog(dialog) {
|
||||
if (!dialog) {
|
||||
_finished = true;
|
||||
return;
|
||||
}
|
||||
connect(_dialog, SIGNAL(destroyed()), this, SLOT(onDestroyed()));
|
||||
}
|
||||
|
||||
ModalDialogListener::~ModalDialogListener() {
|
||||
if (_dialog) {
|
||||
disconnect(_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ModalDialogListener::waitForResult() {
|
||||
while (!_finished) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
|
||||
void ModalDialogListener::onDestroyed() {
|
||||
_finished = true;
|
||||
disconnect(_dialog);
|
||||
_dialog = nullptr;
|
||||
}
|
||||
|
||||
#include "OffscreenUi.moc"
|
||||
|
|
|
@ -30,6 +30,27 @@ class VrMenu;
|
|||
|
||||
#define OFFSCREEN_VISIBILITY_PROPERTY "shown"
|
||||
|
||||
class ModalDialogListener : public QObject {
|
||||
Q_OBJECT
|
||||
friend class OffscreenUi;
|
||||
|
||||
protected:
|
||||
ModalDialogListener(QQuickItem* dialog);
|
||||
virtual ~ModalDialogListener();
|
||||
virtual QVariant waitForResult();
|
||||
|
||||
signals:
|
||||
void response(const QVariant& value);
|
||||
|
||||
protected slots:
|
||||
void onDestroyed();
|
||||
|
||||
protected:
|
||||
QQuickItem* _dialog;
|
||||
bool _finished { false };
|
||||
QVariant _result;
|
||||
};
|
||||
|
||||
class OffscreenUi : public OffscreenQmlSurface, public Dependency {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -71,6 +92,7 @@ public:
|
|||
|
||||
// Message box compatibility
|
||||
Q_INVOKABLE QMessageBox::StandardButton messageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
|
||||
Q_INVOKABLE ModalDialogListener* asyncMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
|
||||
// Must be called from the main thread
|
||||
QQuickItem* createMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
|
||||
// Must be called from the main thread
|
||||
|
@ -88,18 +110,39 @@ public:
|
|||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return information(title, text, buttons, defaultButton);
|
||||
}
|
||||
static ModalDialogListener* asyncCritical(void* ignored, const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return asyncCritical(title, text, buttons, defaultButton);
|
||||
}
|
||||
static ModalDialogListener* asyncInformation(void* ignored, const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return asyncInformation(title, text, buttons, defaultButton);
|
||||
}
|
||||
/// Same design as QMessageBox::question(), will block, returns result
|
||||
static QMessageBox::StandardButton question(void* ignored, const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return question(title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
static ModalDialogListener* asyncQuestion(void* ignored, const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return asyncQuestion(title, text, buttons, defaultButton);
|
||||
}
|
||||
/// Same design as QMessageBox::warning(), will block, returns result
|
||||
static QMessageBox::StandardButton warning(void* ignored, const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return warning(title, text, buttons, defaultButton);
|
||||
}
|
||||
static ModalDialogListener* asyncWarning(void* ignored, const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
|
||||
return asyncWarning(title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
static QMessageBox::StandardButton critical(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
|
@ -107,30 +150,55 @@ public:
|
|||
static QMessageBox::StandardButton information(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static ModalDialogListener* asyncCritical(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static ModalDialogListener *asyncInformation(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static QMessageBox::StandardButton question(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static ModalDialogListener* asyncQuestion (const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static QMessageBox::StandardButton warning(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static ModalDialogListener *asyncWarning(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
|
||||
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
Q_INVOKABLE ModalDialogListener* fileOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
Q_INVOKABLE ModalDialogListener* fileSaveDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
Q_INVOKABLE ModalDialogListener* existingDirectoryDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
Q_INVOKABLE ModalDialogListener* assetOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
// Compatibility with QFileDialog::getOpenFileName
|
||||
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
static ModalDialogListener* getOpenFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
// Compatibility with QFileDialog::getSaveFileName
|
||||
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
static ModalDialogListener* getSaveFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
// Compatibility with QFileDialog::getExistingDirectory
|
||||
static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
static ModalDialogListener* getExistingDirectoryAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
static ModalDialogListener* getOpenAssetNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
|
||||
|
||||
Q_INVOKABLE QVariant inputDialog(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
|
||||
Q_INVOKABLE ModalDialogListener* inputDialogAsync(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
|
||||
Q_INVOKABLE QVariant customInputDialog(const Icon icon, const QString& title, const QVariantMap& config);
|
||||
Q_INVOKABLE ModalDialogListener* customInputDialogAsync(const Icon icon, const QString& title, const QVariantMap& config);
|
||||
QQuickItem* createInputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current);
|
||||
QQuickItem* createCustomInputDialog(const Icon icon, const QString& title, const QVariantMap& config);
|
||||
QVariant waitForInputDialogResult(QQuickItem* inputDialog);
|
||||
|
@ -148,21 +216,47 @@ public:
|
|||
return getItem(OffscreenUi::ICON_NONE, title, label, items, current, editable, ok);
|
||||
}
|
||||
|
||||
// Compatibility with QInputDialog::getText
|
||||
static ModalDialogListener* getTextAsync(void* ignored, const QString & title, const QString & label,
|
||||
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0,
|
||||
Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
|
||||
return getTextAsync(OffscreenUi::ICON_NONE, title, label, text);
|
||||
}
|
||||
// Compatibility with QInputDialog::getItem
|
||||
static ModalDialogListener* getItemAsync(void *ignored, const QString & title, const QString & label, const QStringList & items,
|
||||
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0,
|
||||
Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
|
||||
return getItemAsync(OffscreenUi::ICON_NONE, title, label, items, current, editable);
|
||||
}
|
||||
|
||||
static QString getText(const Icon icon, const QString & title, const QString & label, const QString & text = QString(), bool * ok = 0);
|
||||
static QString getItem(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true, bool * ok = 0);
|
||||
static QVariant getCustomInfo(const Icon icon, const QString& title, const QVariantMap& config, bool* ok = 0);
|
||||
static ModalDialogListener* getTextAsync(const Icon icon, const QString & title, const QString & label, const QString & text = QString());
|
||||
static ModalDialogListener* getItemAsync(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true);
|
||||
static ModalDialogListener* getCustomInfoAsync(const Icon icon, const QString& title, const QVariantMap& config);
|
||||
|
||||
unsigned int getMenuUserDataId() const;
|
||||
QList<QObject *> &getModalDialogListeners();
|
||||
|
||||
signals:
|
||||
void showDesktop();
|
||||
// void response(QMessageBox::StandardButton response);
|
||||
// void fileDialogResponse(QString response);
|
||||
// void assetDialogResponse(QString response);
|
||||
// void inputDialogResponse(QVariant response);
|
||||
public slots:
|
||||
void removeModalDialog(QObject* modal);
|
||||
|
||||
private:
|
||||
QString fileDialog(const QVariantMap& properties);
|
||||
ModalDialogListener *fileDialogAsync(const QVariantMap &properties);
|
||||
QString assetDialog(const QVariantMap& properties);
|
||||
ModalDialogListener* assetDialogAsync(const QVariantMap& properties);
|
||||
|
||||
QQuickItem* _desktop { nullptr };
|
||||
QQuickItem* _toolWindow { nullptr };
|
||||
QList<QObject*> _modalDialogListeners;
|
||||
std::unordered_map<int, bool> _pressedKeys;
|
||||
VrMenu* _vrMenu { nullptr };
|
||||
QQueue<std::function<void(VrMenu*)>> _queuedMenuInitializers;
|
||||
|
|
|
@ -375,7 +375,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle
|
|||
calibrateFromHandController(inputCalibrationData);
|
||||
calibrateFromUI(inputCalibrationData);
|
||||
|
||||
updateCalibratedLimbs();
|
||||
updateCalibratedLimbs(inputCalibrationData);
|
||||
_lastSimPoseData = _nextSimPoseData;
|
||||
}
|
||||
|
||||
|
@ -676,40 +676,55 @@ void ViveControllerManager::InputDevice::uncalibrate() {
|
|||
_overrideHands = false;
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
|
||||
_poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT);
|
||||
_poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(controller::RIGHT_FOOT);
|
||||
_poseStateMap[controller::HIPS] = addOffsetToPuckPose(controller::HIPS);
|
||||
_poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2);
|
||||
_poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM);
|
||||
_poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM);
|
||||
void ViveControllerManager::InputDevice::updateCalibratedLimbs(const controller::InputCalibrationData& inputCalibration) {
|
||||
_poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(inputCalibration, controller::LEFT_FOOT);
|
||||
_poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(inputCalibration, controller::RIGHT_FOOT);
|
||||
_poseStateMap[controller::HIPS] = addOffsetToPuckPose(inputCalibration, controller::HIPS);
|
||||
_poseStateMap[controller::SPINE2] = addOffsetToPuckPose(inputCalibration, controller::SPINE2);
|
||||
_poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(inputCalibration, controller::RIGHT_ARM);
|
||||
_poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(inputCalibration, controller::LEFT_ARM);
|
||||
|
||||
if (_overrideHead) {
|
||||
_poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD);
|
||||
_poseStateMap[controller::HEAD] = addOffsetToPuckPose(inputCalibration, controller::HEAD);
|
||||
}
|
||||
|
||||
if (_overrideHands) {
|
||||
_poseStateMap[controller::LEFT_HAND] = addOffsetToPuckPose(controller::LEFT_HAND);
|
||||
_poseStateMap[controller::RIGHT_HAND] = addOffsetToPuckPose(controller::RIGHT_HAND);
|
||||
_poseStateMap[controller::LEFT_HAND] = addOffsetToPuckPose(inputCalibration, controller::LEFT_HAND);
|
||||
_poseStateMap[controller::RIGHT_HAND] = addOffsetToPuckPose(inputCalibration, controller::RIGHT_HAND);
|
||||
}
|
||||
}
|
||||
|
||||
controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const {
|
||||
controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(const controller::InputCalibrationData& inputCalibration, int joint) const {
|
||||
auto puck = _jointToPuckMap.find(joint);
|
||||
if (puck != _jointToPuckMap.end()) {
|
||||
uint32_t puckIndex = puck->second;
|
||||
auto puckPose = _poseStateMap.find(puckIndex);
|
||||
auto puckPostOffset = _pucksPostOffset.find(puckIndex);
|
||||
auto puckPreOffset = _pucksPreOffset.find(puckIndex);
|
||||
|
||||
if (puckPose != _poseStateMap.end()) {
|
||||
if (puckPreOffset != _pucksPreOffset.end() && puckPostOffset != _pucksPostOffset.end()) {
|
||||
return puckPose->second.postTransform(puckPostOffset->second).transform(puckPreOffset->second);
|
||||
} else if (puckPostOffset != _pucksPostOffset.end()) {
|
||||
return puckPose->second.postTransform(puckPostOffset->second);
|
||||
} else if (puckPreOffset != _pucksPreOffset.end()) {
|
||||
return puckPose->second.transform(puckPreOffset->second);
|
||||
// use sensor space pose.
|
||||
auto puckPoseIter = _validTrackedObjects.begin();
|
||||
while (puckPoseIter != _validTrackedObjects.end()) {
|
||||
if (puckPoseIter->first == puckIndex) {
|
||||
break;
|
||||
}
|
||||
puckPoseIter++;
|
||||
}
|
||||
|
||||
//auto puckPoseIter = _poseStateMap.find(puckIndex);
|
||||
|
||||
if (puckPoseIter != _validTrackedObjects.end()) {
|
||||
|
||||
glm::mat4 postMat; // identity
|
||||
auto postIter = _pucksPostOffset.find(puckIndex);
|
||||
if (postIter != _pucksPostOffset.end()) {
|
||||
postMat = postIter->second;
|
||||
}
|
||||
|
||||
glm::mat4 preMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat;
|
||||
auto preIter = _pucksPreOffset.find(puckIndex);
|
||||
if (preIter != _pucksPreOffset.end()) {
|
||||
preMat = preMat * preIter->second;
|
||||
}
|
||||
|
||||
return puckPoseIter->second.postTransform(postMat).transform(preMat);
|
||||
}
|
||||
}
|
||||
return controller::Pose();
|
||||
|
@ -924,15 +939,12 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint
|
|||
|
||||
void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat,
|
||||
const vec3& linearVelocity, const vec3& angularVelocity) {
|
||||
|
||||
//perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z
|
||||
glm::mat4 matYFlip = mat * Matrices::Y_180;
|
||||
controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity);
|
||||
|
||||
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat;
|
||||
controller::Pose hmdHeadPose = pose.transform(sensorToAvatar);
|
||||
_poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset);
|
||||
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
_poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar);
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
|
||||
|
|
|
@ -79,10 +79,10 @@ private:
|
|||
void sendUserActivityData(QString activity);
|
||||
void configureCalibrationSettings(const QJsonObject configurationSettings);
|
||||
QJsonObject configurationSettings();
|
||||
controller::Pose addOffsetToPuckPose(int joint) const;
|
||||
controller::Pose addOffsetToPuckPose(const controller::InputCalibrationData& inputCalibration, int joint) const;
|
||||
glm::mat4 calculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration);
|
||||
glm::mat4 calculateDefaultToReferenceForHmd(const controller::InputCalibrationData& inputCalibration);
|
||||
void updateCalibratedLimbs();
|
||||
void updateCalibratedLimbs(const controller::InputCalibrationData& inputCalibration);
|
||||
bool checkForCalibrationEvent();
|
||||
void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand);
|
||||
void handleHmd(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick,
|
||||
controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true,
|
||||
LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES,
|
||||
getGrabPointSphereOffset, HMD, MyAvatar, Messages
|
||||
getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities
|
||||
*/
|
||||
|
||||
controllerDispatcherPlugins = {};
|
||||
|
@ -27,6 +27,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ;
|
||||
|
||||
var PROFILE = false;
|
||||
var DEBUG = true;
|
||||
|
||||
if (typeof Test !== "undefined") {
|
||||
PROFILE = true;
|
||||
|
@ -195,7 +196,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
var h;
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
if (controllerLocations[h].valid) {
|
||||
var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor);
|
||||
var nearbyOverlays =
|
||||
Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor);
|
||||
nearbyOverlays.sort(function (a, b) {
|
||||
var aPosition = Overlays.getProperty(a, "position");
|
||||
var aDistance = Vec3.distance(aPosition, controllerLocations[h].position);
|
||||
|
@ -236,6 +238,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
RayPick.getPrevRayPickResult(_this.leftControllerHudRayPick),
|
||||
RayPick.getPrevRayPickResult(_this.rightControllerHudRayPick)
|
||||
];
|
||||
var mouseRayPick = RayPick.getPrevRayPickResult(_this.mouseRayPick);
|
||||
// if the pickray hit something very nearby, put it into the nearby entities list
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
|
||||
|
@ -264,6 +267,20 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
});
|
||||
}
|
||||
|
||||
// sometimes, during a HMD snap-turn, an equipped or held item wont be near
|
||||
// the hand when the findEntities is done. Gather up any hand-children here.
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
var handChildrenIDs = findHandChildEntities(h);
|
||||
handChildrenIDs.forEach(function (handChildID) {
|
||||
if (handChildID in nearbyEntityPropertiesByID) {
|
||||
return;
|
||||
}
|
||||
var props = Entities.getEntityProperties(handChildID, DISPATCHER_PROPERTIES);
|
||||
props.id = handChildID;
|
||||
nearbyEntityPropertiesByID[handChildID] = props;
|
||||
});
|
||||
}
|
||||
|
||||
// bundle up all the data about the current situation
|
||||
var controllerData = {
|
||||
triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue],
|
||||
|
@ -274,7 +291,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
nearbyEntityPropertiesByID: nearbyEntityPropertiesByID,
|
||||
nearbyOverlayIDs: nearbyOverlayIDs,
|
||||
rayPicks: rayPicks,
|
||||
hudRayPicks: hudRayPicks
|
||||
hudRayPicks: hudRayPicks,
|
||||
mouseRayPick: mouseRayPick
|
||||
};
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.gather");
|
||||
|
@ -298,6 +316,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
// activity-slots which this plugin consumes as "in use"
|
||||
_this.runningPluginNames[orderedPluginName] = true;
|
||||
_this.markSlots(candidatePlugin, orderedPluginName);
|
||||
if (DEBUG) {
|
||||
print("controllerDispatcher running " + orderedPluginName);
|
||||
}
|
||||
}
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.isReady." + orderedPluginName);
|
||||
|
@ -330,6 +351,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
// of running plugins and mark its activity-slots as "not in use"
|
||||
delete _this.runningPluginNames[runningPluginName];
|
||||
_this.markSlots(plugin, false);
|
||||
if (DEBUG) {
|
||||
print("controllerDispatcher stopping " + runningPluginName);
|
||||
}
|
||||
}
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.run." + runningPluginName);
|
||||
|
@ -390,6 +414,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE,
|
||||
posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand, true)
|
||||
});
|
||||
this.mouseRayPick = RayPick.createRayPick({
|
||||
joint: "Mouse",
|
||||
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
this.handleHandMessage = function(channel, message, sender) {
|
||||
var data;
|
||||
|
|
|
@ -254,6 +254,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
this.triggerValue = 0;
|
||||
this.messageGrabEntity = false;
|
||||
this.grabEntityProps = null;
|
||||
this.shouldSendStart = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
300,
|
||||
|
@ -498,6 +499,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
var cloneID = this.cloneHotspot(grabbedProperties, controllerData);
|
||||
this.targetEntityID = cloneID;
|
||||
Entities.editEntity(this.targetEntityID, reparentProps);
|
||||
controllerData.nearbyEntityPropertiesByID[this.targetEntityID] = grabbedProperties;
|
||||
isClone = true;
|
||||
} else if (!grabbedProperties.locked) {
|
||||
Entities.editEntity(this.targetEntityID, reparentProps);
|
||||
|
@ -507,8 +509,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
return;
|
||||
}
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "startEquip", args);
|
||||
// we don't want to send startEquip message until the trigger is released. otherwise,
|
||||
// guns etc will fire right as they are equipped.
|
||||
this.shouldSendStart = true;
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'equip',
|
||||
|
@ -588,22 +591,21 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
// if the potentialHotspot is cloneable, clone it and return it
|
||||
// if the potentialHotspot os not cloneable and locked return null
|
||||
|
||||
if (potentialEquipHotspot) {
|
||||
if ((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.targetEntityID = this.grabbedHotspot.entityID;
|
||||
this.startEquipEntity(controllerData);
|
||||
this.messageGrabEnity = false;
|
||||
}
|
||||
if (potentialEquipHotspot &&
|
||||
((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity)) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.targetEntityID = this.grabbedHotspot.entityID;
|
||||
this.startEquipEntity(controllerData);
|
||||
this.messageGrabEnity = false;
|
||||
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
|
||||
} else {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.isTargetIDValid = function() {
|
||||
var entityProperties = Entities.getEntityProperties(this.targetEntityID, ["type"]);
|
||||
return "type" in entityProperties;
|
||||
this.isTargetIDValid = function(controllerData) {
|
||||
var entityProperties = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||
return entityProperties && "type" in entityProperties;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData, deltaTime) {
|
||||
|
@ -616,7 +618,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
var timestamp = Date.now();
|
||||
this.updateInputs(controllerData);
|
||||
|
||||
if (!this.isTargetIDValid()) {
|
||||
if (!this.isTargetIDValid(controllerData)) {
|
||||
this.endEquipEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
@ -643,6 +645,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
var dropDetected = this.dropGestureProcess(deltaTime);
|
||||
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
if (this.shouldSendStart) {
|
||||
// we don't want to send startEquip message until the trigger is released. otherwise,
|
||||
// guns etc will fire right as they are equipped.
|
||||
var startArgs = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "startEquip", startArgs);
|
||||
this.shouldSendStart = false;
|
||||
}
|
||||
this.waitForTriggerRelease = false;
|
||||
}
|
||||
|
||||
|
@ -674,8 +683,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
|
||||
equipHotspotBuddy.update(deltaTime, timestamp, controllerData);
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueEquip", args);
|
||||
if (!this.shouldSendStart) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueEquip", args);
|
||||
}
|
||||
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
|
||||
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
|
||||
AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
|
||||
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays
|
||||
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
|
||||
|
||||
*/
|
||||
|
||||
|
@ -21,7 +21,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() {
|
||||
|
||||
var PICK_WITH_HAND_RAY = true;
|
||||
|
||||
var halfPath = {
|
||||
|
@ -423,19 +422,9 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
}
|
||||
};
|
||||
|
||||
this.isPointingAtUI = function(controllerData) {
|
||||
var hudRayPickInfo = controllerData.hudRayPicks[this.hand];
|
||||
var hudPoint2d = HMD.overlayFromWorldPoint(hudRayPickInfo.intersection);
|
||||
if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
|
||||
this.notPointingAtEntity(controllerData) || this.isPointingAtUI(controllerData)) {
|
||||
this.notPointingAtEntity(controllerData)) {
|
||||
this.endNearGrabAction();
|
||||
this.laserPointerOff();
|
||||
return makeRunningValues(false, [], []);
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
//
|
||||
// hudOverlayPointer.js
|
||||
//
|
||||
// scripts/system/controllers/controllerModules/
|
||||
//
|
||||
// Created by Dante Ruiz 2017-9-21
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat,
|
||||
getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, Entities, NULL_UUID,
|
||||
enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable,
|
||||
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
|
||||
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
|
||||
AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
|
||||
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
|
||||
|
||||
*/
|
||||
(function() {
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
var ControllerDispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
var halfPath = {
|
||||
type: "line3d",
|
||||
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
|
||||
visible: true,
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
glow: 1.0,
|
||||
lineWidth: 5,
|
||||
ignoreRayIntersection: true, // always ignore this
|
||||
drawHUDLayer: true, // Even when burried inside of something, show it.
|
||||
parentID: AVATAR_SELF_ID
|
||||
};
|
||||
var halfEnd = {
|
||||
type: "sphere",
|
||||
solid: true,
|
||||
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
|
||||
alpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
drawHUDLayer: true, // Even when burried inside of something, show it.
|
||||
visible: true
|
||||
};
|
||||
var fullPath = {
|
||||
type: "line3d",
|
||||
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
|
||||
visible: true,
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
glow: 1.0,
|
||||
lineWidth: 5,
|
||||
ignoreRayIntersection: true, // always ignore this
|
||||
drawHUDLayer: true, // Even when burried inside of something, show it.
|
||||
parentID: AVATAR_SELF_ID
|
||||
};
|
||||
var fullEnd = {
|
||||
type: "sphere",
|
||||
solid: true,
|
||||
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
|
||||
alpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
drawHUDLayer: true, // Even when burried inside of something, show it.
|
||||
visible: true
|
||||
};
|
||||
var holdPath = {
|
||||
type: "line3d",
|
||||
color: COLORS_GRAB_DISTANCE_HOLD,
|
||||
visible: true,
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
glow: 1.0,
|
||||
lineWidth: 5,
|
||||
ignoreRayIntersection: true, // always ignore this
|
||||
drawHUDLayer: true, // Even when burried inside of something, show it.
|
||||
parentID: AVATAR_SELF_ID
|
||||
};
|
||||
|
||||
var renderStates = [
|
||||
{name: "half", path: halfPath, end: halfEnd},
|
||||
{name: "full", path: fullPath, end: fullEnd},
|
||||
{name: "hold", path: holdPath}
|
||||
];
|
||||
|
||||
var defaultRenderStates = [
|
||||
{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath},
|
||||
{name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath},
|
||||
{name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}
|
||||
];
|
||||
|
||||
var MARGIN = 25;
|
||||
|
||||
function HudOverlayPointer(hand) {
|
||||
var _this = this;
|
||||
this.hand = hand;
|
||||
this.reticleMinX = MARGIN;
|
||||
this.reticleMaxX;
|
||||
this.reticleMinY = MARGIN;
|
||||
this.reticleMaxY;
|
||||
this.clicked = false;
|
||||
this.triggerClicked = 0;
|
||||
this.movedAway = false;
|
||||
this.parameters = ControllerDispatcherUtils.makeDispatcherModuleParameters(
|
||||
540,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
this.getOtherHandController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
|
||||
};
|
||||
|
||||
_this.isClicked = function() {
|
||||
return _this.triggerClicked;
|
||||
};
|
||||
|
||||
this.handToController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
};
|
||||
|
||||
this.updateRecommendedArea = function() {
|
||||
var dims = Controller.getViewportDimensions();
|
||||
this.reticleMaxX = dims.x - MARGIN;
|
||||
this.reticleMaxY = dims.y - MARGIN;
|
||||
};
|
||||
|
||||
this.updateLaserPointer = function(controllerData) {
|
||||
var RADIUS = 0.005;
|
||||
var dim = { x: RADIUS, y: RADIUS, z: RADIUS };
|
||||
|
||||
if (this.mode === "full") {
|
||||
this.fullEnd.dimensions = dim;
|
||||
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd});
|
||||
} else if (this.mode === "half") {
|
||||
this.halfEnd.dimensions = dim;
|
||||
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd});
|
||||
}
|
||||
|
||||
LaserPointers.enableLaserPointer(this.laserPointer);
|
||||
LaserPointers.setRenderState(this.laserPointer, this.mode);
|
||||
};
|
||||
|
||||
this.processControllerTriggers = function(controllerData) {
|
||||
if (controllerData.triggerClicks[this.hand]) {
|
||||
this.mode = "full";
|
||||
} else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
||||
this.clicked = false;
|
||||
this.mode = "half";
|
||||
} else {
|
||||
this.mode = "none";
|
||||
}
|
||||
};
|
||||
|
||||
this.calculateNewReticlePosition = function(intersection) {
|
||||
this.updateRecommendedArea();
|
||||
var point2d = HMD.overlayFromWorldPoint(intersection);
|
||||
point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX));
|
||||
point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY));
|
||||
return point2d;
|
||||
};
|
||||
|
||||
this.setReticlePosition = function(point2d) {
|
||||
Reticle.setPosition(point2d);
|
||||
};
|
||||
|
||||
this.pointingAtTablet = function(controllerData) {
|
||||
var rayPick = controllerData.rayPicks[this.hand];
|
||||
return (rayPick.objectID === HMD.tabletScreenID || rayPick.objectID === HMD.homeButtonID);
|
||||
};
|
||||
|
||||
this.processLaser = function(controllerData) {
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
if ((controllerData.triggerValues[this.hand] < ControllerDispatcherUtils.TRIGGER_ON_VALUE || !controllerLocation.valid) ||
|
||||
this.pointingAtTablet(controllerData)) {
|
||||
this.exitModule();
|
||||
return false;
|
||||
}
|
||||
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
|
||||
this.setReticlePosition(point2d);
|
||||
if (!Reticle.isPointingAtSystemOverlay(point2d)) {
|
||||
this.exitModule();
|
||||
return false;
|
||||
}
|
||||
Reticle.visible = false;
|
||||
this.movedAway = false;
|
||||
this.triggerClicked = controllerData.triggerClicks[this.hand];
|
||||
this.processControllerTriggers(controllerData);
|
||||
this.updateLaserPointer(controllerData);
|
||||
return true;
|
||||
};
|
||||
|
||||
this.exitModule = function() {
|
||||
LaserPointers.disableLaserPointer(this.laserPointer);
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
if (this.processLaser(controllerData)) {
|
||||
return ControllerDispatcherUtils.makeRunningValues(true, [], []);
|
||||
} else {
|
||||
return ControllerDispatcherUtils.makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
return this.isReady(controllerData);
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
LaserPointers.disableLaserPointer(this.laserPointer);
|
||||
LaserPointers.removeLaserPointer(this.laserPointer);
|
||||
};
|
||||
|
||||
this.halfEnd = halfEnd;
|
||||
this.fullEnd = fullEnd;
|
||||
this.laserPointer = LaserPointers.createLaserPointer({
|
||||
joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND",
|
||||
filter: RayPick.PICK_HUD,
|
||||
maxDistance: PICK_MAX_DISTANCE,
|
||||
posOffset: getGrabPointSphereOffset(this.handToController(), true),
|
||||
renderStates: renderStates,
|
||||
enabled: true,
|
||||
defaultRenderStates: defaultRenderStates
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var leftHudOverlayPointer = new HudOverlayPointer(LEFT_HAND);
|
||||
var rightHudOverlayPointer = new HudOverlayPointer(RIGHT_HAND);
|
||||
|
||||
var clickMapping = Controller.newMapping('HudOverlayPointer-click');
|
||||
clickMapping.from(rightHudOverlayPointer.isClicked).to(Controller.Actions.ReticleClick);
|
||||
clickMapping.from(leftHudOverlayPointer.isClicked).to(Controller.Actions.ReticleClick);
|
||||
clickMapping.enable();
|
||||
|
||||
ControllerDispatcherUtils.enableDispatcherModule("LeftHudOverlayPointer", leftHudOverlayPointer);
|
||||
ControllerDispatcherUtils.enableDispatcherModule("RightHudOverlayPointer", rightHudOverlayPointer);
|
||||
|
||||
function cleanup() {
|
||||
ControllerDispatcherUtils.disableDispatcherModule("LeftHudOverlayPointer");
|
||||
ControllerDispatcherUtils.disbaleDispatcherModule("RightHudOverlayPointer");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
})();
|
137
scripts/system/controllers/controllerModules/mouseHMD.js
Normal file
137
scripts/system/controllers/controllerModules/mouseHMD.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
//
|
||||
// mouseHMD.js
|
||||
//
|
||||
// scripts/system/controllers/controllerModules/
|
||||
//
|
||||
// Created by Dante Ruiz 2017-9-22
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
(function() {
|
||||
var ControllerDispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
function TimeLock(experation) {
|
||||
this.experation = experation;
|
||||
this.last = 0;
|
||||
this.update = function(time) {
|
||||
this.last = time || Date.now();
|
||||
};
|
||||
|
||||
this.expired = function(time) {
|
||||
return ((time || Date.now()) - this.last) > this.experation;
|
||||
};
|
||||
}
|
||||
|
||||
function MouseHMD() {
|
||||
var _this = this;
|
||||
this.mouseMoved = false;
|
||||
this.mouseActivity = new TimeLock(5000);
|
||||
this.handControllerActivity = new TimeLock(4000);
|
||||
this.parameters = ControllerDispatcherUtils.makeDispatcherModuleParameters(
|
||||
10,
|
||||
["mouse"],
|
||||
[],
|
||||
100);
|
||||
|
||||
this.onMouseMove = function() {
|
||||
_this.updateMouseActivity();
|
||||
};
|
||||
|
||||
this.onMouseClick = function() {
|
||||
_this.updateMouseActivity();
|
||||
};
|
||||
|
||||
this.updateMouseActivity = function(isClick) {
|
||||
if (_this.ignoreMouseActivity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (HMD.active) {
|
||||
var now = Date.now();
|
||||
_this.mouseActivity.update(now);
|
||||
}
|
||||
};
|
||||
|
||||
this.adjustReticleDepth = function(controllerData) {
|
||||
if (Reticle.isPointingAtSystemOverlay(Reticle.position)) {
|
||||
var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position);
|
||||
Reticle.depth = Vec3.distance(reticlePositionOnHUD, HMD.position);
|
||||
} else {
|
||||
var APPARENT_MAXIMUM_DEPTH = 100.0;
|
||||
var result = controllerData.mouseRayPick;
|
||||
Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH;
|
||||
}
|
||||
};
|
||||
|
||||
this.ignoreMouseActivity = function() {
|
||||
if (!Reticle.allowMouseCapture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var pos = Reticle.position;
|
||||
if (!pos || (pos.x === -1 && pos.y === -1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_this.handControllerActivity.expired()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.triggersPressed = function(controllerData, now) {
|
||||
var onValue = ControllerDispatcherUtils.TRIGGER_ON_VALUE;
|
||||
var rightHand = ControllerDispatcherUtils.RIGHT_HAND;
|
||||
var leftHand = ControllerDispatcherUtils.LEFT_HAND;
|
||||
var leftTriggerValue = controllerData.triggerValues[leftHand];
|
||||
var rightTriggerValue = controllerData.triggerValues[rightHand];
|
||||
|
||||
if (leftTriggerValue > onValue || rightTriggerValue > onValue) {
|
||||
this.handControllerActivity.update(now);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.isReady = function(controllerData, deltaTime) {
|
||||
var now = Date.now();
|
||||
this.triggersPressed(controllerData, now);
|
||||
if ((HMD.active && !this.mouseActivity.expired(now)) && _this.handControllerActivity.expired()) {
|
||||
Reticle.visible = true;
|
||||
return ControllerDispatcherUtils.makeRunningValues(true, [], []);
|
||||
}
|
||||
if (HMD.active) {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
|
||||
return ControllerDispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function(controllerData, deltaTime) {
|
||||
var now = Date.now();
|
||||
if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now)) {
|
||||
Reticle.visible = false;
|
||||
return ControllerDispatcherUtils.makeRunningValues(false, [], []);
|
||||
}
|
||||
this.adjustReticleDepth(controllerData);
|
||||
return ControllerDispatcherUtils.makeRunningValues(true, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var mouseHMD = new MouseHMD();
|
||||
ControllerDispatcherUtils.enableDispatcherModule("MouseHMD", mouseHMD);
|
||||
|
||||
Controller.mouseMoveEvent.connect(mouseHMD.onMouseMove);
|
||||
Controller.mousePressEvent.connect(mouseHMD.onMouseClick);
|
||||
|
||||
function cleanup() {
|
||||
ControllerDispatcherUtils.disableDispatcherModule("MouseHMD");
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
})();
|
|
@ -182,7 +182,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
}
|
||||
|
||||
if (targetProps) {
|
||||
if (!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) {
|
||||
if ((!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) ||
|
||||
targetProps.parentID != NULL_UUID) {
|
||||
return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it
|
||||
} else {
|
||||
this.targetEntityID = targetProps.id;
|
||||
|
@ -216,7 +217,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
|
||||
var targetProps = this.getTargetProps(controllerData);
|
||||
if (targetProps) {
|
||||
if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||
if (controllerData.triggerClicks[this.hand] ||
|
||||
controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||
// switch to grabbing
|
||||
var targetCloneable = entityIsCloneable(targetProps);
|
||||
if (targetCloneable) {
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID,
|
||||
enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
|
||||
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGroupParent,
|
||||
Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE
|
||||
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
|
||||
findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
|
||||
HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
|
||||
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
@ -28,6 +30,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
this.previousParentJointIndex = {};
|
||||
this.previouslyUnhooked = {};
|
||||
this.hapticTargetID = null;
|
||||
this.lastUnequipCheckTime = 0;
|
||||
this.autoUnequipCounter = 0;
|
||||
this.lastUnexpectedChildrenCheckTime = 0;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
500,
|
||||
|
@ -40,11 +45,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
this.controllerJointIndex = getControllerJointIndex(this.hand);
|
||||
|
||||
this.getOtherModule = function() {
|
||||
return (this.hand === RIGHT_HAND) ? leftNearParentingGrabEntity : rightNearParentingGrabEntity;
|
||||
};
|
||||
|
||||
this.thisHandIsParent = function(props) {
|
||||
if (!props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) {
|
||||
return false;
|
||||
}
|
||||
|
@ -93,8 +98,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
|
||||
if (this.thisHandIsParent(targetProps)) {
|
||||
// this should never happen, but if it does, don't set previous parent to be this hand.
|
||||
// this.previousParentID[targetProps.id] = NULL;
|
||||
// this.previousParentJointIndex[targetProps.id] = -1;
|
||||
this.previousParentID[targetProps.id] = null;
|
||||
this.previousParentJointIndex[targetProps.id] = -1;
|
||||
} else {
|
||||
this.previousParentID[targetProps.id] = targetProps.parentID;
|
||||
this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex;
|
||||
|
@ -111,20 +116,24 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
this.grabbing = true;
|
||||
};
|
||||
|
||||
this.endNearParentingGrabEntity = function () {
|
||||
if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) {
|
||||
Entities.editEntity(this.targetEntityID, {
|
||||
parentID: this.previousParentID[this.targetEntityID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.targetEntityID]
|
||||
});
|
||||
} else {
|
||||
// we're putting this back as a child of some other parent, so zero its velocity
|
||||
Entities.editEntity(this.targetEntityID, {
|
||||
parentID: this.previousParentID[this.targetEntityID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.targetEntityID],
|
||||
localVelocity: {x: 0, y: 0, z: 0},
|
||||
localAngularVelocity: {x: 0, y: 0, z: 0}
|
||||
});
|
||||
this.endNearParentingGrabEntity = function (controllerData) {
|
||||
this.hapticTargetID = null;
|
||||
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||
if (this.thisHandIsParent(props)) {
|
||||
if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) {
|
||||
Entities.editEntity(this.targetEntityID, {
|
||||
parentID: this.previousParentID[this.targetEntityID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.targetEntityID]
|
||||
});
|
||||
} else {
|
||||
// we're putting this back as a child of some other parent, so zero its velocity
|
||||
Entities.editEntity(this.targetEntityID, {
|
||||
parentID: this.previousParentID[this.targetEntityID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.targetEntityID],
|
||||
localVelocity: {x: 0, y: 0, z: 0},
|
||||
localAngularVelocity: {x: 0, y: 0, z: 0}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
|
@ -133,6 +142,71 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
this.targetEntityID = null;
|
||||
};
|
||||
|
||||
this.checkForChildTooFarAway = function (controllerData) {
|
||||
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||
var now = Date.now();
|
||||
if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * TEAR_AWAY_CHECK_TIME) {
|
||||
this.lastUnequipCheckTime = now;
|
||||
if (props.parentID == AVATAR_SELF_ID) {
|
||||
var handPosition = controllerData.controllerLocations[this.hand].position;
|
||||
var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props);
|
||||
if (dist > TEAR_AWAY_DISTANCE) {
|
||||
this.autoUnequipCounter++;
|
||||
} else {
|
||||
this.autoUnequipCounter = 0;
|
||||
}
|
||||
if (this.autoUnequipCounter >= TEAR_AWAY_COUNT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
this.checkForUnexpectedChildren = function (controllerData) {
|
||||
// sometimes things can get parented to a hand and this script is unaware. Search for such entities and
|
||||
// unhook them.
|
||||
|
||||
var now = Date.now();
|
||||
var UNEXPECTED_CHILDREN_CHECK_TIME = 0.1; // seconds
|
||||
if (now - this.lastUnexpectedChildrenCheckTime > MSECS_PER_SEC * UNEXPECTED_CHILDREN_CHECK_TIME) {
|
||||
this.lastUnexpectedChildrenCheckTime = now;
|
||||
|
||||
var children = findHandChildEntities(this.hand);
|
||||
var _this = this;
|
||||
|
||||
children.forEach(function(childID) {
|
||||
// we appear to be holding something and this script isn't in a state that would be holding something.
|
||||
// unhook it. if we previously took note of this entity's parent, put it back where it was. This
|
||||
// works around some problems that happen when more than one hand or avatar is passing something around.
|
||||
if (_this.previousParentID[childID]) {
|
||||
var previousParentID = _this.previousParentID[childID];
|
||||
var previousParentJointIndex = _this.previousParentJointIndex[childID];
|
||||
|
||||
// The main flaw with keeping track of previous parantage in individual scripts is:
|
||||
// (1) A grabs something (2) B takes it from A (3) A takes it from B (4) A releases it
|
||||
// now A and B will take turns passing it back to the other. Detect this and stop the loop here...
|
||||
var UNHOOK_LOOP_DETECT_MS = 200;
|
||||
if (_this.previouslyUnhooked[childID]) {
|
||||
if (now - _this.previouslyUnhooked[childID] < UNHOOK_LOOP_DETECT_MS) {
|
||||
previousParentID = NULL_UUID;
|
||||
previousParentJointIndex = -1;
|
||||
}
|
||||
}
|
||||
_this.previouslyUnhooked[childID] = now;
|
||||
|
||||
Entities.editEntity(childID, {
|
||||
parentID: previousParentID,
|
||||
parentJointIndex: previousParentJointIndex
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(childID, { parentID: NULL_UUID });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.getTargetProps = function (controllerData) {
|
||||
// nearbyEntityProperties is already sorted by length from controller
|
||||
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
|
||||
|
@ -168,11 +242,13 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
var targetProps = this.getTargetProps(controllerData);
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE &&
|
||||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
this.checkForUnexpectedChildren(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
if (targetProps) {
|
||||
if (propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) {
|
||||
if ((propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) &&
|
||||
targetProps.parentID == NULL_UUID) {
|
||||
return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it
|
||||
} else {
|
||||
this.targetEntityID = targetProps.id;
|
||||
|
@ -188,16 +264,23 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
if (this.grabbing) {
|
||||
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
|
||||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
this.endNearParentingGrabEntity();
|
||||
this.endNearParentingGrabEntity(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||
if (!props) {
|
||||
// entity was deleted
|
||||
this.grabbing = false;
|
||||
this.targetEntityID = null;
|
||||
this.hapticTargetID = null;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var props = Entities.getEntityProperties(this.targetEntityID);
|
||||
if (!this.thisHandIsParent(props)) {
|
||||
this.grabbing = false;
|
||||
this.targetEntityID = null;
|
||||
this.hapticTargetID = null;
|
||||
if (this.checkForChildTooFarAway(controllerData)) {
|
||||
// if the held entity moves too far from the hand, release it
|
||||
print("nearParentGrabEntity -- autoreleasing held item because it is far from hand");
|
||||
this.endNearParentingGrabEntity(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
|
@ -205,7 +288,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args);
|
||||
} else {
|
||||
// still searching / highlighting
|
||||
var readiness = this.isReady (controllerData);
|
||||
var readiness = this.isReady(controllerData);
|
||||
if (!readiness.active) {
|
||||
return readiness;
|
||||
}
|
||||
|
@ -218,7 +301,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
|
||||
var cloneID = cloneEntity(targetProps, worldEntityProps);
|
||||
var cloneProps = Entities.getEntityProperties(cloneID);
|
||||
|
||||
this.grabbing = true;
|
||||
this.targetEntityID = cloneID;
|
||||
this.startNearParentingGrabEntity(controllerData, cloneProps);
|
||||
|
|
|
@ -88,13 +88,8 @@ Script.include("/~/system/libraries/utils.js");
|
|||
this.startNearParentingGrabOverlay = function (controllerData) {
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
|
||||
var handJointIndex;
|
||||
// if (this.ignoreIK) {
|
||||
// handJointIndex = this.controllerJointIndex;
|
||||
// } else {
|
||||
// handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
// }
|
||||
handJointIndex = this.controllerJointIndex;
|
||||
this.controllerJointIndex = getControllerJointIndex(this.hand);
|
||||
var handJointIndex = this.controllerJointIndex;
|
||||
|
||||
var grabbedProperties = this.getGrabbedProperties();
|
||||
|
||||
|
|
|
@ -76,9 +76,9 @@
|
|||
dispatcherUtils.enableDispatcherModule("LeftScaleAvatar", leftScaleAvatar);
|
||||
dispatcherUtils.enableDispatcherModule("RightScaleAvatar", rightScaleAvatar);
|
||||
|
||||
this.cleanup = function() {
|
||||
function cleanup() {
|
||||
dispatcherUtils.disableDispatcherModule("LeftScaleAvatar");
|
||||
dispatcherUtils.disableDispatcherModule("RightScaleAvatar");
|
||||
};
|
||||
Script.scriptEnding.connect(this.cleanup);
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
})();
|
||||
|
|
|
@ -248,10 +248,17 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
}
|
||||
};
|
||||
|
||||
this.nearGrabWantsToRun = function(controllerData) {
|
||||
var moduleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
|
||||
var module = getEnabledModuleByName(moduleName);
|
||||
var ready = module ? module.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
return ready.active;
|
||||
};
|
||||
|
||||
this.processStylus = function(controllerData) {
|
||||
this.updateStylusTip();
|
||||
|
||||
if (!this.stylusTip.valid || this.overlayLaserActive(controllerData)) {
|
||||
if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.nearGrabWantsToRun(controllerData)) {
|
||||
this.pointFinger(false);
|
||||
this.hideStylus();
|
||||
return false;
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
var CONTOLLER_SCRIPTS = [
|
||||
"squeezeHands.js",
|
||||
"controllerDisplayManager.js",
|
||||
"handControllerPointer.js",
|
||||
"grab.js",
|
||||
"toggleAdvancedMovementForHandControllers.js",
|
||||
"controllerDispatcher.js",
|
||||
|
@ -30,6 +29,8 @@ var CONTOLLER_SCRIPTS = [
|
|||
"controllerModules/farTrigger.js",
|
||||
"controllerModules/teleport.js",
|
||||
"controllerModules/scaleAvatar.js",
|
||||
"controllerModules/hudOverlayPointer.js",
|
||||
"controllerModules/mouseHMD.js",
|
||||
"controllerModules/scaleEntity.js"
|
||||
];
|
||||
|
||||
|
|
|
@ -1,706 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// handControllerPointer.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Howard Stearns on 2016/04/22
|
||||
// Copyright 2016 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
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
// Control the "mouse" using hand controller. (HMD and desktop.)
|
||||
// First-person only.
|
||||
// Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed.
|
||||
// (For now, the thumb buttons on both controllers are always on.)
|
||||
// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand
|
||||
// controller beam intersects the HUD.
|
||||
|
||||
var activeTrigger;
|
||||
function isLaserOn() {
|
||||
return activeTrigger.partial();
|
||||
}
|
||||
Script.include("../libraries/controllers.js");
|
||||
|
||||
// UTILITIES -------------
|
||||
//
|
||||
function ignore() { }
|
||||
|
||||
// Utility to make it easier to setup and disconnect cleanly.
|
||||
function setupHandler(event, handler) {
|
||||
event.connect(handler);
|
||||
Script.scriptEnding.connect(function () {
|
||||
event.disconnect(handler);
|
||||
});
|
||||
}
|
||||
|
||||
// If some capability is not available until expiration milliseconds after the last update.
|
||||
function TimeLock(expiration) {
|
||||
var last = 0;
|
||||
this.update = function (optionalNow) {
|
||||
last = optionalNow || Date.now();
|
||||
};
|
||||
this.expired = function (optionalNow) {
|
||||
return ((optionalNow || Date.now()) - last) > expiration;
|
||||
};
|
||||
}
|
||||
|
||||
var handControllerLockOut = new TimeLock(2000);
|
||||
|
||||
function Trigger(label) {
|
||||
// This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this.
|
||||
var that = this;
|
||||
that.label = label;
|
||||
that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing
|
||||
that.TRIGGER_OFF_VALUE = 0.10;
|
||||
that.TRIGGER_ON_VALUE = that.TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab
|
||||
that.rawTriggerValue = 0;
|
||||
that.triggerValue = 0; // rolling average of trigger value
|
||||
that.triggerClicked = false;
|
||||
that.triggerClick = function (value) { that.triggerClicked = value; };
|
||||
that.triggerPress = function (value) { that.rawTriggerValue = value; };
|
||||
that.updateSmoothedTrigger = function () { // e.g., call once/update for effect
|
||||
var triggerValue = that.rawTriggerValue;
|
||||
// smooth out trigger value
|
||||
that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) +
|
||||
(triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO));
|
||||
OffscreenFlags.navigationFocusDisabled = that.triggerValue != 0.0;
|
||||
};
|
||||
// Current smoothed state, without hysteresis. Answering booleans.
|
||||
that.triggerSmoothedClick = function () {
|
||||
return that.triggerClicked;
|
||||
};
|
||||
that.triggerSmoothedSqueezed = function () {
|
||||
return that.triggerValue > that.TRIGGER_ON_VALUE;
|
||||
};
|
||||
that.triggerSmoothedReleased = function () {
|
||||
return that.triggerValue < that.TRIGGER_OFF_VALUE;
|
||||
};
|
||||
|
||||
// This part is not from handControllerGrab.js
|
||||
that.state = null; // tri-state: falsey, 'partial', 'full'
|
||||
that.update = function () { // update state, called from an update function
|
||||
var state = that.state;
|
||||
that.updateSmoothedTrigger();
|
||||
|
||||
// The first two are independent of previous state:
|
||||
if (that.triggerSmoothedClick()) {
|
||||
state = 'full';
|
||||
} else if (that.triggerSmoothedReleased()) {
|
||||
state = null;
|
||||
} else if (that.triggerSmoothedSqueezed()) {
|
||||
// Another way to do this would be to have hysteresis in this branch, but that seems to make things harder to use.
|
||||
// In particular, the vive has a nice detent as you release off of full, and we want that to be a transition from
|
||||
// full to partial.
|
||||
state = 'partial';
|
||||
}
|
||||
that.state = state;
|
||||
};
|
||||
// Answer a controller source function (answering either 0.0 or 1.0).
|
||||
that.partial = function () {
|
||||
return that.state ? 1.0 : 0.0; // either 'partial' or 'full'
|
||||
};
|
||||
that.full = function () {
|
||||
return (that.state === 'full') ? 1.0 : 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
// VERTICAL FIELD OF VIEW ---------
|
||||
//
|
||||
// Cache the verticalFieldOfView setting and update it every so often.
|
||||
var verticalFieldOfView, DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees
|
||||
function updateFieldOfView() {
|
||||
verticalFieldOfView = Settings.getValue('fieldOfView') || DEFAULT_VERTICAL_FIELD_OF_VIEW;
|
||||
}
|
||||
|
||||
// SHIMS ----------
|
||||
//
|
||||
var weMovedReticle = false;
|
||||
function ignoreMouseActivity() {
|
||||
// If we're paused, or if change in cursor position is from this script, not the hardware mouse.
|
||||
if (!Reticle.allowMouseCapture) {
|
||||
return true;
|
||||
}
|
||||
var pos = Reticle.position;
|
||||
if (!pos || (pos.x == -1 && pos.y == -1)) {
|
||||
return true;
|
||||
}
|
||||
// Only we know if we moved it, which is why this script has to replace depthReticle.js
|
||||
if (!weMovedReticle) {
|
||||
return false;
|
||||
}
|
||||
weMovedReticle = false;
|
||||
return true;
|
||||
}
|
||||
var MARGIN = 25;
|
||||
var reticleMinX = MARGIN, reticleMaxX, reticleMinY = MARGIN, reticleMaxY;
|
||||
function updateRecommendedArea() {
|
||||
var dims = Controller.getViewportDimensions();
|
||||
reticleMaxX = dims.x - MARGIN;
|
||||
reticleMaxY = dims.y - MARGIN;
|
||||
}
|
||||
var setReticlePosition = function (point2d) {
|
||||
weMovedReticle = true;
|
||||
point2d.x = Math.max(reticleMinX, Math.min(point2d.x, reticleMaxX));
|
||||
point2d.y = Math.max(reticleMinY, Math.min(point2d.y, reticleMaxY));
|
||||
Reticle.setPosition(point2d);
|
||||
};
|
||||
|
||||
// VISUAL AID -----------
|
||||
// Same properties as handControllerGrab search sphere
|
||||
var LASER_ALPHA = 0.5;
|
||||
var LASER_SEARCH_COLOR = {red: 10, green: 10, blue: 255};
|
||||
var LASER_TRIGGER_COLOR = {red: 250, green: 10, blue: 10};
|
||||
var END_DIAMETER = 0.05;
|
||||
var systemLaserOn = false;
|
||||
|
||||
var triggerPath = {
|
||||
type: "line3d",
|
||||
color: LASER_TRIGGER_COLOR,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
alpha: LASER_ALPHA,
|
||||
solid: true,
|
||||
glow: 1.0,
|
||||
drawHUDLayer: true
|
||||
}
|
||||
var triggerEnd = {
|
||||
type: "sphere",
|
||||
dimensions: {x: END_DIAMETER, y: END_DIAMETER, z: END_DIAMETER},
|
||||
color: LASER_TRIGGER_COLOR,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
alpha: LASER_ALPHA,
|
||||
solid: true,
|
||||
drawHUDLayer: true
|
||||
}
|
||||
|
||||
var searchPath = {
|
||||
type: "line3d",
|
||||
color: LASER_SEARCH_COLOR,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
alpha: LASER_ALPHA,
|
||||
solid: true,
|
||||
glow: 1.0,
|
||||
drawHUDLayer: true
|
||||
}
|
||||
var searchEnd = {
|
||||
type: "sphere",
|
||||
dimensions: {x: END_DIAMETER, y: END_DIAMETER, z: END_DIAMETER},
|
||||
color: LASER_SEARCH_COLOR,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
alpha: LASER_ALPHA,
|
||||
solid: true,
|
||||
drawHUDLayer: true
|
||||
}
|
||||
|
||||
var hudRayStates = [{name: "trigger", path: triggerPath, end: triggerEnd},
|
||||
{name: "search", path: searchPath, end: searchEnd}];
|
||||
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
|
||||
var GRAB_POINT_SPHERE_OFFSET_RIGHT = { x: 0.04, y: 0.13, z: 0.039 };
|
||||
var GRAB_POINT_SPHERE_OFFSET_LEFT = { x: -0.04, y: 0.13, z: 0.039 };
|
||||
var hudRayRight = LaserPointers.createLaserPointer({
|
||||
joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
|
||||
filter: RayPick.PICK_HUD,
|
||||
posOffset: GRAB_POINT_SPHERE_OFFSET_RIGHT,
|
||||
renderStates: hudRayStates,
|
||||
enabled: true
|
||||
});
|
||||
var hudRayLeft = LaserPointers.createLaserPointer({
|
||||
joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
|
||||
filter: RayPick.PICK_HUD,
|
||||
posOffset: GRAB_POINT_SPHERE_OFFSET_LEFT,
|
||||
renderStates: hudRayStates,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// NOTE: keep this offset in sync with scripts/system/librarires/controllers.js:57
|
||||
var VERTICAL_HEAD_LASER_OFFSET = 0.1;
|
||||
var hudRayHead = LaserPointers.createLaserPointer({
|
||||
joint: "Avatar",
|
||||
filter: RayPick.PICK_HUD,
|
||||
posOffset: {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0},
|
||||
renderStates: hudRayStates,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
var mouseRayPick = RayPick.createRayPick({
|
||||
joint: "Mouse",
|
||||
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
function isPointingAtOverlay(optionalHudPosition2d) {
|
||||
return Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(optionalHudPosition2d || Reticle.position);
|
||||
}
|
||||
|
||||
// Generalized HUD utilities, with or without HMD:
|
||||
// This "var" is for documentation. Do not change the value!
|
||||
var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1;
|
||||
function calculateRayUICollisionPoint(position, direction, isHands) {
|
||||
// Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection.
|
||||
if (HMD.active) {
|
||||
var laserPointer;
|
||||
if (isHands) {
|
||||
laserPointer = activeHand == Controller.Standard.RightHand ? hudRayRight : hudRayLeft;
|
||||
} else {
|
||||
laserPointer = hudRayHead;
|
||||
}
|
||||
var result = LaserPointers.getPrevRayPickResult(laserPointer);
|
||||
if (result.type != RayPick.INTERSECTED_NONE) {
|
||||
return result.intersection;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// interect HUD plane, 1m in front of camera, using formula:
|
||||
// scale = hudNormal dot (hudPoint - position) / hudNormal dot direction
|
||||
// intersection = postion + scale*direction
|
||||
var hudNormal = Quat.getForward(Camera.getOrientation());
|
||||
var hudPoint = Vec3.sum(Camera.getPosition(), hudNormal); // must also scale if PLANAR_PERPENDICULAR_HUD_DISTANCE!=1
|
||||
var denominator = Vec3.dot(hudNormal, direction);
|
||||
if (denominator === 0) {
|
||||
return null;
|
||||
} // parallel to plane
|
||||
var numerator = Vec3.dot(hudNormal, Vec3.subtract(hudPoint, position));
|
||||
var scale = numerator / denominator;
|
||||
return Vec3.sum(position, Vec3.multiply(scale, direction));
|
||||
}
|
||||
var DEGREES_TO_HALF_RADIANS = Math.PI / 360;
|
||||
function overlayFromWorldPoint(point) {
|
||||
// Answer the 2d pixel-space location in the HUD that covers the given 3D point.
|
||||
// REQUIRES: that the 3d point be on the hud surface!
|
||||
// Note that this is based on the Camera, and doesn't know anything about any
|
||||
// ray that may or may not have been used to compute the point. E.g., the
|
||||
// overlay point is NOT the intersection of some non-camera ray with the HUD.
|
||||
if (HMD.active) {
|
||||
return HMD.overlayFromWorldPoint(point);
|
||||
}
|
||||
var cameraToPoint = Vec3.subtract(point, Camera.getPosition());
|
||||
var cameraX = Vec3.dot(cameraToPoint, Quat.getRight(Camera.getOrientation()));
|
||||
var cameraY = Vec3.dot(cameraToPoint, Quat.getUp(Camera.getOrientation()));
|
||||
var size = Controller.getViewportDimensions();
|
||||
var hudHeight = 2 * Math.tan(verticalFieldOfView * DEGREES_TO_HALF_RADIANS); // must adjust if PLANAR_PERPENDICULAR_HUD_DISTANCE!=1
|
||||
var hudWidth = hudHeight * size.x / size.y;
|
||||
var horizontalFraction = (cameraX / hudWidth + 0.5);
|
||||
var verticalFraction = 1 - (cameraY / hudHeight + 0.5);
|
||||
var horizontalPixels = size.x * horizontalFraction;
|
||||
var verticalPixels = size.y * verticalFraction;
|
||||
return { x: horizontalPixels, y: verticalPixels };
|
||||
}
|
||||
|
||||
var gamePad = Controller.findDevice("GamePad");
|
||||
function activeHudPoint2dGamePad() {
|
||||
if (!HMD.active) {
|
||||
return;
|
||||
}
|
||||
var headPosition = MyAvatar.getHeadPosition();
|
||||
var headDirection = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })));
|
||||
|
||||
var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection, false);
|
||||
|
||||
if (!hudPoint3d) {
|
||||
if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here
|
||||
print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong.
|
||||
}
|
||||
return;
|
||||
}
|
||||
var hudPoint2d = overlayFromWorldPoint(hudPoint3d);
|
||||
|
||||
// We don't know yet if we'll want to make the cursor or laser visble, but we need to move it to see if
|
||||
// it's pointing at a QML tool (aka system overlay).
|
||||
setReticlePosition(hudPoint2d);
|
||||
|
||||
return hudPoint2d;
|
||||
}
|
||||
|
||||
|
||||
function activeHudPoint2d(activeHand) { // if controller is valid, update reticle position and answer 2d point. Otherwise falsey.
|
||||
var controllerPose = getControllerWorldLocation(activeHand, true); // note: this will return head pose if hand pose is invalid (third eye)
|
||||
if (!controllerPose.valid) {
|
||||
return; // Controller is cradled.
|
||||
}
|
||||
var controllerPosition = controllerPose.position;
|
||||
var controllerDirection = Quat.getUp(controllerPose.rotation);
|
||||
|
||||
var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection, true);
|
||||
if (!hudPoint3d) {
|
||||
if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here
|
||||
print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong.
|
||||
}
|
||||
return;
|
||||
}
|
||||
var hudPoint2d = overlayFromWorldPoint(hudPoint3d);
|
||||
|
||||
// We don't know yet if we'll want to make the cursor or laser visble, but we need to move it to see if
|
||||
// it's pointing at a QML tool (aka system overlay).
|
||||
setReticlePosition(hudPoint2d);
|
||||
return hudPoint2d;
|
||||
}
|
||||
|
||||
// MOUSE ACTIVITY --------
|
||||
//
|
||||
var isSeeking = false;
|
||||
var averageMouseVelocity = 0, lastIntegration = 0, lastMouse;
|
||||
var WEIGHTING = 1 / 20; // simple moving average over last 20 samples
|
||||
var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
|
||||
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20;
|
||||
function isShakingMouse() { // True if the person is waving the mouse around trying to find it.
|
||||
var now = Date.now(), mouse = Reticle.position, isShaking = false;
|
||||
if (lastIntegration && (lastIntegration !== now)) {
|
||||
var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration);
|
||||
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity);
|
||||
if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) {
|
||||
isShaking = true;
|
||||
}
|
||||
}
|
||||
lastIntegration = now;
|
||||
lastMouse = mouse;
|
||||
return isShaking;
|
||||
}
|
||||
var NON_LINEAR_DIVISOR = 2;
|
||||
var MINIMUM_SEEK_DISTANCE = 0.1;
|
||||
function updateSeeking(doNotStartSeeking) {
|
||||
if (!doNotStartSeeking && !isLaserOn() && (!Reticle.visible || isShakingMouse())) {
|
||||
isSeeking = true;
|
||||
} // e.g., if we're about to turn it on with first movement.
|
||||
if (!isSeeking) {
|
||||
return;
|
||||
}
|
||||
averageMouseVelocity = lastIntegration = 0;
|
||||
var lookAt2D = HMD.getHUDLookAtPosition2D();
|
||||
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
||||
isSeeking = false;
|
||||
return; // E.g., if parallel to location in HUD
|
||||
}
|
||||
var copy = Reticle.position;
|
||||
function updateDimension(axis) {
|
||||
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
||||
var move = distanceBetween / NON_LINEAR_DIVISOR;
|
||||
if (Math.abs(move) < MINIMUM_SEEK_DISTANCE) {
|
||||
return false;
|
||||
}
|
||||
copy[axis] += move;
|
||||
return true;
|
||||
}
|
||||
var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit.
|
||||
if (okX && okY) {
|
||||
isSeeking = false;
|
||||
} else {
|
||||
Reticle.setPosition(copy); // Not setReticlePosition
|
||||
}
|
||||
}
|
||||
|
||||
var mouseCursorActivity = new TimeLock(5000);
|
||||
var APPARENT_MAXIMUM_DEPTH = 100.0; // this is a depth at which things all seem sufficiently distant
|
||||
function updateMouseActivity(isClick) {
|
||||
if (ignoreMouseActivity()) {
|
||||
return;
|
||||
}
|
||||
var now = Date.now();
|
||||
mouseCursorActivity.update(now);
|
||||
if (isClick) {
|
||||
return;
|
||||
} // Bug: mouse clicks should keep going. Just not hand controller clicks
|
||||
handControllerLockOut.update(now);
|
||||
Reticle.visible = true;
|
||||
}
|
||||
function expireMouseCursor(now) {
|
||||
if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
}
|
||||
function hudReticleDistance() { // 3d distance from camera to the reticle position on hud
|
||||
// (The camera is only in the center of the sphere on reset.)
|
||||
var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position);
|
||||
return Vec3.distance(reticlePositionOnHUD, HMD.position);
|
||||
}
|
||||
|
||||
function maybeAdjustReticleDepth() {
|
||||
if (HMD.active) { // set depth
|
||||
if (isPointingAtOverlay()) {
|
||||
Reticle.depth = hudReticleDistance();
|
||||
}
|
||||
}
|
||||
}
|
||||
var ADJUST_RETICLE_DEPTH_INTERVAL = 50; // 20hz
|
||||
Script.setInterval(maybeAdjustReticleDepth,ADJUST_RETICLE_DEPTH_INTERVAL);
|
||||
|
||||
function onMouseMove() {
|
||||
// Display cursor at correct depth (as in depthReticle.js), and updateMouseActivity.
|
||||
if (ignoreMouseActivity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (HMD.active) { // set depth
|
||||
updateSeeking();
|
||||
if (isPointingAtOverlay()) {
|
||||
Reticle.depth = hudReticleDistance();
|
||||
} else {
|
||||
var result = RayPick.getPrevRayPickResult(mouseRayPick);
|
||||
Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH;
|
||||
}
|
||||
}
|
||||
updateMouseActivity(); // After the above, just in case the depth movement is awkward when becoming visible.
|
||||
}
|
||||
function onMouseClick() {
|
||||
updateMouseActivity(true);
|
||||
}
|
||||
setupHandler(Controller.mouseMoveEvent, onMouseMove);
|
||||
setupHandler(Controller.mousePressEvent, onMouseClick);
|
||||
setupHandler(Controller.mouseDoublePressEvent, onMouseClick);
|
||||
|
||||
// CONTROLLER MAPPING ---------
|
||||
//
|
||||
|
||||
var leftTrigger = new Trigger('left');
|
||||
var rightTrigger = new Trigger('right');
|
||||
activeTrigger = rightTrigger;
|
||||
var activeHand = Controller.Standard.RightHand;
|
||||
var LEFT_HUD_LASER = 1;
|
||||
var RIGHT_HUD_LASER = 2;
|
||||
var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER;
|
||||
var activeHudLaser = RIGHT_HUD_LASER;
|
||||
function toggleHand() { // unequivocally switch which hand controls mouse position
|
||||
if (activeHand === Controller.Standard.RightHand) {
|
||||
activeHand = Controller.Standard.LeftHand;
|
||||
activeTrigger = leftTrigger;
|
||||
activeHudLaser = LEFT_HUD_LASER;
|
||||
} else {
|
||||
activeHand = Controller.Standard.RightHand;
|
||||
activeTrigger = rightTrigger;
|
||||
activeHudLaser = RIGHT_HUD_LASER;
|
||||
}
|
||||
clearSystemLaser();
|
||||
}
|
||||
function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1
|
||||
return function (on) {
|
||||
if (on && (activeHand !== hand)) {
|
||||
toggleHand();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var clickMapping = Controller.newMapping('handControllerPointer-click');
|
||||
Script.scriptEnding.connect(clickMapping.disable);
|
||||
|
||||
// Gather the trigger data for smoothing.
|
||||
clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress);
|
||||
clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress);
|
||||
clickMapping.from(Controller.Standard.RTClick).peek().to(rightTrigger.triggerClick);
|
||||
clickMapping.from(Controller.Standard.LTClick).peek().to(leftTrigger.triggerClick);
|
||||
// Full smoothed trigger is a click.
|
||||
function isPointingAtOverlayStartedNonFullTrigger(trigger) {
|
||||
// true if isPointingAtOverlay AND we were NOT full triggered when we became so.
|
||||
// The idea is to not count clicks when we're full-triggering and reach the edge of a window.
|
||||
var lockedIn = false;
|
||||
return function () {
|
||||
if (trigger !== activeTrigger) {
|
||||
return lockedIn = false;
|
||||
}
|
||||
if (!isPointingAtOverlay()) {
|
||||
return lockedIn = false;
|
||||
}
|
||||
if (lockedIn) {
|
||||
return true;
|
||||
}
|
||||
lockedIn = !trigger.full();
|
||||
return lockedIn;
|
||||
}
|
||||
}
|
||||
clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(rightTrigger)).to(Controller.Actions.ReticleClick);
|
||||
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
||||
// The following is essentially like Left and Right versions of
|
||||
// clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||
// except that we first update the reticle position from the appropriate hand position, before invoking the .
|
||||
var wantsMenu = 0;
|
||||
clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu);
|
||||
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) {
|
||||
if (clicked) {
|
||||
activeHudPoint2d(Controller.Standard.RightHand);
|
||||
Messages.sendLocalMessage("toggleHand", Controller.Standard.RightHand);
|
||||
}
|
||||
wantsMenu = clicked;
|
||||
});
|
||||
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (clicked) {
|
||||
if (clicked) {
|
||||
activeHudPoint2d(Controller.Standard.LeftHand);
|
||||
Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand);
|
||||
}
|
||||
wantsMenu = clicked;
|
||||
});
|
||||
clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) {
|
||||
if (clicked) {
|
||||
activeHudPoint2dGamePad();
|
||||
var noHands = -1;
|
||||
Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand);
|
||||
}
|
||||
|
||||
wantsMenu = clicked;
|
||||
});
|
||||
clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () {
|
||||
// Allow the reticle depth to be set correctly:
|
||||
// Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move
|
||||
// so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove.
|
||||
// We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove
|
||||
// after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse.
|
||||
Script.setTimeout(function () {
|
||||
var noHands = -1;
|
||||
Messages.sendLocalMessage("toggleHand", noHands);
|
||||
Reticle.setPosition(Reticle.position);
|
||||
}, 0);
|
||||
});
|
||||
// Partial smoothed trigger is activation.
|
||||
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
||||
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
||||
clickMapping.enable();
|
||||
|
||||
var HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable";
|
||||
var isPointerEnabled = true;
|
||||
|
||||
function clearSystemLaser() {
|
||||
if (!systemLaserOn) {
|
||||
return;
|
||||
}
|
||||
HMD.deactivateHMDHandMouse();
|
||||
LaserPointers.setRenderState(hudRayRight, "");
|
||||
LaserPointers.setRenderState(hudRayLeft, "");
|
||||
LaserPointers.setRenderState(hudRayHead, "");
|
||||
systemLaserOn = false;
|
||||
weMovedReticle = true;
|
||||
}
|
||||
function setColoredLaser() { // answer trigger state if lasers supported, else falsey.
|
||||
var mode = (activeTrigger.state === 'full') ? 'trigger' : 'search';
|
||||
|
||||
if (!systemLaserOn) {
|
||||
HMD.activateHMDHandMouse();
|
||||
}
|
||||
|
||||
var pose = Controller.getPoseValue(activeHand);
|
||||
if (!pose.valid) {
|
||||
LaserPointers.setRenderState(hudRayRight, "");
|
||||
LaserPointers.setRenderState(hudRayLeft, "");
|
||||
LaserPointers.setRenderState(hudRayHead, mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
var right = activeHand == Controller.Standard.RightHand;
|
||||
LaserPointers.setRenderState(hudRayRight, right ? mode : "");
|
||||
LaserPointers.setRenderState(hudRayLeft, right ? "" : mode);
|
||||
LaserPointers.setRenderState(hudRayHead, "");
|
||||
|
||||
return activeTrigger.state;
|
||||
}
|
||||
|
||||
// MAIN OPERATIONS -----------
|
||||
//
|
||||
function update() {
|
||||
var now = Date.now();
|
||||
function off() {
|
||||
expireMouseCursor();
|
||||
clearSystemLaser();
|
||||
}
|
||||
|
||||
updateSeeking(true);
|
||||
if (!handControllerLockOut.expired(now)) {
|
||||
return off(); // Let them use mouse in peace.
|
||||
}
|
||||
|
||||
if ((!Window.hasFocus() && !HMD.active) || !Reticle.allowMouseCapture) {
|
||||
// In desktop it's pretty clear when another app is on top. In that case we bail, because
|
||||
// hand controllers might be sputtering "valid" data and that will keep someone from deliberately
|
||||
// using the mouse on another app. (Fogbugz case 546.)
|
||||
// However, in HMD, you might not realize you're not on top, and you wouldn't be able to operate
|
||||
// other apps anyway. So in that case, we DO keep going even though we're not on top. (Fogbugz 1831.)
|
||||
return off(); // Don't mess with other apps or paused mouse activity
|
||||
}
|
||||
|
||||
leftTrigger.update();
|
||||
rightTrigger.update();
|
||||
if (!activeTrigger.state) {
|
||||
return off(); // No trigger
|
||||
}
|
||||
|
||||
if (getGrabCommunications()) {
|
||||
return off();
|
||||
}
|
||||
|
||||
|
||||
var hudPoint2d = activeHudPoint2d(activeHand);
|
||||
if (!hudPoint2d) {
|
||||
return off();
|
||||
}
|
||||
|
||||
// If there's a HUD element at the (newly moved) reticle, just make it visible and bail.
|
||||
if (isPointingAtOverlay(hudPoint2d) && isPointerEnabled) {
|
||||
if (HMD.active) {
|
||||
Reticle.depth = hudReticleDistance();
|
||||
|
||||
var pose = Controller.getPoseValue(activeHand);
|
||||
if (!pose.valid) {
|
||||
var mode = (activeTrigger.state === 'full') ? 'trigger' : 'search';
|
||||
if (!systemLaserOn) {
|
||||
HMD.activateHMDHandMouse();
|
||||
}
|
||||
LaserPointers.setRenderState(hudRayHead, mode);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color
|
||||
// If the active plugin doesn't implement hand lasers, show the mouse reticle instead.
|
||||
systemLaserOn = setColoredLaser();
|
||||
Reticle.visible = !systemLaserOn;
|
||||
} else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) {
|
||||
clearSystemLaser();
|
||||
Reticle.visible = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We are not pointing at a HUD element (but it could be a 3d overlay).
|
||||
clearSystemLaser();
|
||||
Reticle.visible = false;
|
||||
}
|
||||
|
||||
// Check periodically for changes to setup.
|
||||
var SETTINGS_CHANGE_RECHECK_INTERVAL = 10 * 1000; // 10 seconds
|
||||
function checkSettings() {
|
||||
updateFieldOfView();
|
||||
updateRecommendedArea();
|
||||
}
|
||||
checkSettings();
|
||||
|
||||
// Enable/disable pointer.
|
||||
function handleMessages(channel, message, sender) {
|
||||
if (sender === MyAvatar.sessionUUID && channel === HIFI_POINTER_DISABLE_MESSAGE_CHANNEL) {
|
||||
var data = JSON.parse(message);
|
||||
if (data.pointerEnabled !== undefined) {
|
||||
print("pointerEnabled: " + data.pointerEnabled);
|
||||
isPointerEnabled = data.pointerEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.connect(handleMessages);
|
||||
|
||||
var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(function () {
|
||||
Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.disconnect(handleMessages);
|
||||
Script.clearInterval(settingsChecker);
|
||||
Script.update.disconnect(update);
|
||||
OffscreenFlags.navigationFocusDisabled = false;
|
||||
LaserPointers.removeLaserPointer(hudRayRight);
|
||||
LaserPointers.removeLaserPointer(hudRayLeft);
|
||||
LaserPointers.removeLaserPointer(hudRayHead);
|
||||
HMD.deactivateHMDHandMouse();
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -247,9 +247,12 @@ var toolBar = (function () {
|
|||
direction = MyAvatar.orientation;
|
||||
}
|
||||
direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z);
|
||||
|
||||
// Align entity with Avatar orientation.
|
||||
properties.rotation = MyAvatar.orientation;
|
||||
|
||||
var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"];
|
||||
if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
|
||||
|
||||
// Adjust position of entity per bounding box prior to creating it.
|
||||
var registration = properties.registration;
|
||||
if (registration === undefined) {
|
||||
|
@ -259,7 +262,14 @@ var toolBar = (function () {
|
|||
|
||||
var orientation = properties.orientation;
|
||||
if (orientation === undefined) {
|
||||
var DEFAULT_ORIENTATION = Quat.fromPitchYawRollDegrees(0, 0, 0);
|
||||
properties.orientation = MyAvatar.orientation;
|
||||
var DEFAULT_ORIENTATION = properties.orientation;
|
||||
orientation = DEFAULT_ORIENTATION;
|
||||
} else {
|
||||
// If the orientation is already defined, we perform the corresponding rotation assuming that
|
||||
// our start referential is the avatar referential.
|
||||
properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation);
|
||||
var DEFAULT_ORIENTATION = properties.orientation;
|
||||
orientation = DEFAULT_ORIENTATION;
|
||||
}
|
||||
|
||||
|
@ -433,17 +443,8 @@ var toolBar = (function () {
|
|||
});
|
||||
|
||||
addButton("importEntitiesButton", "assets-01.svg", function() {
|
||||
var importURL = null;
|
||||
var fullPath = Window.browse("Select Model to Import", "", "*.json");
|
||||
if (fullPath) {
|
||||
importURL = "file:///" + fullPath;
|
||||
}
|
||||
if (importURL) {
|
||||
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
|
||||
toolBar.toggle();
|
||||
}
|
||||
importSVO(importURL);
|
||||
}
|
||||
Window.openFileChanged.connect(onFileOpenChanged);
|
||||
Window.browseAsync("Select Model to Import", "", "*.json");
|
||||
});
|
||||
|
||||
addButton("openAssetBrowserButton", "assets-01.svg", function() {
|
||||
|
@ -1461,6 +1462,39 @@ function toggleSelectedEntitiesVisible() {
|
|||
}
|
||||
}
|
||||
|
||||
function onFileSaveChanged(filename) {
|
||||
Window.saveFileChanged.disconnect(onFileSaveChanged);
|
||||
if (filename !== "") {
|
||||
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
||||
if (!success) {
|
||||
Window.notifyEditError("Export failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onFileOpenChanged(filename) {
|
||||
var importURL = null;
|
||||
if (filename !== "") {
|
||||
importURL = "file:///" + filename;
|
||||
}
|
||||
if (importURL) {
|
||||
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
|
||||
toolBar.toggle();
|
||||
}
|
||||
importSVO(importURL);
|
||||
}
|
||||
}
|
||||
|
||||
function onPromptTextChanged(prompt) {
|
||||
Window.promptTextChanged.disconnect(onPromptTextChanged);
|
||||
if (prompt !== "") {
|
||||
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
|
||||
toolBar.toggle();
|
||||
}
|
||||
importSVO(prompt);
|
||||
}
|
||||
}
|
||||
|
||||
function handeMenuEvent(menuItem) {
|
||||
if (menuItem === "Allow Selecting of Small Models") {
|
||||
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
|
||||
|
@ -1478,30 +1512,16 @@ function handeMenuEvent(menuItem) {
|
|||
if (!selectionManager.hasSelection()) {
|
||||
Window.notifyEditError("No entities have been selected.");
|
||||
} else {
|
||||
var filename = Window.save("Select Where to Save", "", "*.json");
|
||||
if (filename) {
|
||||
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
||||
if (!success) {
|
||||
Window.notifyEditError("Export failed.");
|
||||
}
|
||||
}
|
||||
Window.saveFileChanged.connect(onFileSaveChanged);
|
||||
Window.saveAsync("Select Where to Save", "", "*.json");
|
||||
}
|
||||
} else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") {
|
||||
var importURL = null;
|
||||
if (menuItem === "Import Entities") {
|
||||
var fullPath = Window.browse("Select Model to Import", "", "*.json");
|
||||
if (fullPath) {
|
||||
importURL = "file:///" + fullPath;
|
||||
}
|
||||
Window.openFileChanged.connect(onFileOpenChanged);
|
||||
Window.browseAsync("Select Model to Import", "", "*.json");
|
||||
} else {
|
||||
importURL = Window.prompt("URL of SVO to import", "");
|
||||
}
|
||||
|
||||
if (importURL) {
|
||||
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
|
||||
toolBar.toggle();
|
||||
}
|
||||
importSVO(importURL);
|
||||
Window.promptTextChanged.connect(onFileOpenChanged);
|
||||
Window.promptAsync("URL of SVO to import", "");
|
||||
}
|
||||
} else if (menuItem === "Entity List...") {
|
||||
entityListTool.toggleVisible();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
|
||||
/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays,
|
||||
/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform,
|
||||
MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, NULL_UUID:true, AVATAR_SELF_ID:true, FORBIDDEN_GRAB_TYPES:true,
|
||||
HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
|
||||
DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true,
|
||||
|
@ -40,7 +40,12 @@
|
|||
entityHasActions:true,
|
||||
ensureDynamic:true,
|
||||
findGroupParent:true,
|
||||
BUMPER_ON_VALUE:true
|
||||
BUMPER_ON_VALUE:true,
|
||||
findHandChildEntities:true,
|
||||
TEAR_AWAY_DISTANCE:true,
|
||||
TEAR_AWAY_COUNT:true,
|
||||
TEAR_AWAY_CHECK_TIME:true,
|
||||
distanceBetweenPointAndEntityBoundingBox:true
|
||||
*/
|
||||
|
||||
MSECS_PER_SEC = 1000.0;
|
||||
|
@ -79,6 +84,10 @@ COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 };
|
|||
|
||||
NEAR_GRAB_RADIUS = 1.0;
|
||||
|
||||
TEAR_AWAY_DISTANCE = 0.1; // ungrab an entity if its bounding-box moves this far from the hand
|
||||
TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away
|
||||
TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks
|
||||
|
||||
DISPATCHER_PROPERTIES = [
|
||||
"position",
|
||||
"registrationPoint",
|
||||
|
@ -193,17 +202,6 @@ entityIsDistanceGrabbable = function(props) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// XXX
|
||||
// var distance = Vec3.distance(props.position, handPosition);
|
||||
// this.otherGrabbingUUID = entityIsGrabbedByOther(entityID);
|
||||
// if (this.otherGrabbingUUID !== null) {
|
||||
// // don't distance grab something that is already grabbed.
|
||||
// if (debug) {
|
||||
// print("distance grab is skipping '" + props.name + "': already grabbed by another.");
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -296,8 +294,9 @@ ensureDynamic = function (entityID) {
|
|||
};
|
||||
|
||||
findGroupParent = function (controllerData, targetProps) {
|
||||
while (targetProps.parentID && targetProps.parentID !== NULL_UUID) {
|
||||
// XXX use controllerData.nearbyEntityPropertiesByID ?
|
||||
while (targetProps.parentID &&
|
||||
targetProps.parentID !== NULL_UUID &&
|
||||
Entities.getNestableType(targetProps.parentID) == "entity") {
|
||||
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
|
||||
if (!parentProps) {
|
||||
break;
|
||||
|
@ -310,6 +309,50 @@ findGroupParent = function (controllerData, targetProps) {
|
|||
return targetProps;
|
||||
};
|
||||
|
||||
|
||||
findHandChildEntities = function(hand) {
|
||||
// find children of avatar's hand joint
|
||||
var handJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex);
|
||||
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, handJointIndex));
|
||||
|
||||
// find children of faux controller joint
|
||||
var controllerJointIndex = getControllerJointIndex(hand);
|
||||
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex));
|
||||
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerJointIndex));
|
||||
|
||||
// find children of faux camera-relative controller joint
|
||||
var controllerCRJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
|
||||
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
|
||||
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
|
||||
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerCRJointIndex));
|
||||
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex));
|
||||
|
||||
return children.filter(function (childID) {
|
||||
var childType = Entities.getNestableType(childID);
|
||||
return childType == "entity";
|
||||
});
|
||||
};
|
||||
|
||||
distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) {
|
||||
var entityXform = new Xform(entityProps.rotation, entityProps.position);
|
||||
var localPoint = entityXform.inv().xformPoint(point);
|
||||
var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions);
|
||||
var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions);
|
||||
var localMin = Vec3.subtract(entityXform.trans, minOffset);
|
||||
var localMax = Vec3.sum(entityXform.trans, maxOffset);
|
||||
|
||||
var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z};
|
||||
v.x = Math.max(v.x, localMin.x);
|
||||
v.x = Math.min(v.x, localMax.x);
|
||||
v.y = Math.max(v.y, localMin.y);
|
||||
v.y = Math.min(v.y, localMax.y);
|
||||
v.z = Math.max(v.z, localMin.z);
|
||||
v.z = Math.min(v.z, localMax.z);
|
||||
|
||||
return Vec3.distance(v, localPoint);
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = {
|
||||
makeDispatcherModuleParameters: makeDispatcherModuleParameters,
|
||||
|
@ -319,11 +362,13 @@ if (typeof module !== 'undefined') {
|
|||
LEFT_HAND: LEFT_HAND,
|
||||
RIGHT_HAND: RIGHT_HAND,
|
||||
BUMPER_ON_VALUE: BUMPER_ON_VALUE,
|
||||
TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE,
|
||||
propsArePhysical: propsArePhysical,
|
||||
entityIsGrabbable: entityIsGrabbable,
|
||||
NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS,
|
||||
projectOntoOverlayXYPlane: projectOntoOverlayXYPlane,
|
||||
projectOntoEntityXYPlane: projectOntoEntityXYPlane
|
||||
|
||||
projectOntoEntityXYPlane: projectOntoEntityXYPlane,
|
||||
TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE,
|
||||
TRIGGER_ON_VALUE: TRIGGER_ON_VALUE
|
||||
};
|
||||
}
|
||||
|
|
|
@ -108,6 +108,16 @@ EntityListTool = function(opts) {
|
|||
webView.emitScriptEvent(JSON.stringify(data));
|
||||
};
|
||||
|
||||
function onFileSaveChanged(filename) {
|
||||
Window.saveFileChanged.disconnect(onFileSaveChanged);
|
||||
if (filename !== "") {
|
||||
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
||||
if (!success) {
|
||||
Window.notifyEditError("Export failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webView.webEventReceived.connect(function(data) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
|
@ -139,13 +149,8 @@ EntityListTool = function(opts) {
|
|||
if (!selectionManager.hasSelection()) {
|
||||
Window.notifyEditError("No entities have been selected.");
|
||||
} else {
|
||||
var filename = Window.save("Select Where to Save", "", "*.json");
|
||||
if (filename) {
|
||||
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
||||
if (!success) {
|
||||
Window.notifyEditError("Export failed.");
|
||||
}
|
||||
}
|
||||
Window.saveFileChanged.connect(onFileSaveChanged);
|
||||
Window.saveAsync("Select Where to Save", "", "*.json");
|
||||
}
|
||||
} else if (data.type == "pal") {
|
||||
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
|
||||
|
|
|
@ -249,7 +249,7 @@
|
|||
noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH;
|
||||
noticeHeight = notice.height * NOTIFICATION_3D_SCALE;
|
||||
|
||||
notice.size = { x: noticeWidth, y: noticeHeight};
|
||||
notice.size = { x: noticeWidth, y: noticeHeight };
|
||||
|
||||
positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y);
|
||||
|
||||
|
|
|
@ -113,15 +113,8 @@ function onMessage(message) {
|
|||
openLoginWindow();
|
||||
break;
|
||||
case 'chooseSnapshotLocation':
|
||||
var snapshotPath = Window.browseDir("Choose Snapshots Directory", "", "");
|
||||
|
||||
if (snapshotPath) { // not cancelled
|
||||
Snapshot.setSnapshotsLocation(snapshotPath);
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "snapshotLocationChosen"
|
||||
}));
|
||||
}
|
||||
Window.browseDirChanged.connect(snapshotDirChanged);
|
||||
Window.browseDirAsync("Choose Snapshots Directory", "", "");
|
||||
break;
|
||||
case 'openSettings':
|
||||
if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false))
|
||||
|
@ -572,6 +565,17 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
|
|||
});
|
||||
}
|
||||
|
||||
function snapshotDirChanged(snapshotPath) {
|
||||
Window.browseDirChanged.disconnect(snapshotDirChanged);
|
||||
if (snapshotPath !== "") { // not cancelled
|
||||
Snapshot.setSnapshotsLocation(snapshotPath);
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "snapshotLocationChosen"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function processingGifStarted(pathStillSnapshot) {
|
||||
Window.processingGifStarted.disconnect(processingGifStarted);
|
||||
Window.processingGifCompleted.connect(processingGifCompleted);
|
||||
|
|
|
@ -272,6 +272,36 @@
|
|||
Messages.subscribe("home");
|
||||
Messages.messageReceived.connect(handleMessage);
|
||||
|
||||
var clickMapping = Controller.newMapping('tabletToggle-click');
|
||||
var wantsMenu = 0;
|
||||
clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu);
|
||||
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) {
|
||||
if (clicked) {
|
||||
//activeHudPoint2d(Controller.Standard.RightHand);
|
||||
Messages.sendLocalMessage("toggleHand", Controller.Standard.RightHand);
|
||||
}
|
||||
wantsMenu = clicked;
|
||||
});
|
||||
|
||||
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (clicked) {
|
||||
if (clicked) {
|
||||
//activeHudPoint2d(Controller.Standard.LeftHand);
|
||||
Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand);
|
||||
}
|
||||
wantsMenu = clicked;
|
||||
});
|
||||
|
||||
clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) {
|
||||
if (clicked) {
|
||||
//activeHudPoint2dGamePad();
|
||||
var noHands = -1;
|
||||
Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand);
|
||||
}
|
||||
|
||||
wantsMenu = clicked;
|
||||
});
|
||||
clickMapping.enable();
|
||||
|
||||
Script.setInterval(updateShowTablet, 100);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
|
|
|
@ -486,6 +486,15 @@
|
|||
return isFinishOnOpen;
|
||||
}
|
||||
|
||||
function onAssetsDirChanged(recording) {
|
||||
Window.assetsDirChanged.disconnect(onAssetsDirChanged);
|
||||
if (recording !== "") {
|
||||
log("Load recording " + recording);
|
||||
UserActivityLogger.logAction("record_load_recording", logDetails());
|
||||
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
|
||||
}
|
||||
}
|
||||
|
||||
function onWebEventReceived(data) {
|
||||
var message,
|
||||
recording;
|
||||
|
@ -520,12 +529,8 @@
|
|||
break;
|
||||
case LOAD_RECORDING_ACTION:
|
||||
// User wants to select an ATP recording to play.
|
||||
recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr");
|
||||
if (recording) {
|
||||
log("Load recording " + recording);
|
||||
UserActivityLogger.logAction("record_load_recording", logDetails());
|
||||
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
|
||||
}
|
||||
Window.assetsDirChanged.connect(onAssetsDirChanged);
|
||||
Window.browseAssetsAsync("Select Recording to Play", "recordings", "*.hfr");
|
||||
break;
|
||||
case START_RECORDING_ACTION:
|
||||
// Start making a recording.
|
||||
|
|
Loading…
Reference in a new issue