Merge branch 'game-render-interface' of https://github.com/highfidelity/hifi into blue

This commit is contained in:
samcake 2017-09-26 16:12:27 -07:00
commit 328dd9c108
79 changed files with 1954 additions and 1451 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -78,6 +78,9 @@ signals:
void requestWebSurface();
void releaseWebSurface();
protected:
Transform evalRenderTransform() override;
private:
InputMode _inputMode { Touch };
QSharedPointer<OffscreenQmlSurface> _webSurface;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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