mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 05:52:38 +02:00
Merge branch 'master' of github.com:highfidelity/hifi
This commit is contained in:
commit
83171a7cde
78 changed files with 11105 additions and 39 deletions
Binary file not shown.
After Width: | Height: | Size: 548 KiB |
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compressed": {
|
||||
"COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT": "Default-Sky-9-cubemap-ambient_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT.ktx",
|
||||
"COMPRESSED_SRGB8_ETC2": "Default-Sky-9-cubemap-ambient_COMPRESSED_SRGB8_ETC2.ktx"
|
||||
},
|
||||
"original": "Default-Sky-9-cubemap-ambient.jpg",
|
||||
"uncompressed": "Default-Sky-9-cubemap-ambient.ktx",
|
||||
"version": 1
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -4,5 +4,6 @@
|
|||
"COMPRESSED_SRGB8_ETC2": "Default-Sky-9-cubemap_COMPRESSED_SRGB8_ETC2.ktx"
|
||||
},
|
||||
"original": "Default-Sky-9-cubemap.jpg",
|
||||
"uncompressed": "Default-Sky-9-cubemap.ktx"
|
||||
"uncompressed": "Default-Sky-9-cubemap.ktx",
|
||||
"version": 1
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -76,7 +76,7 @@ Rectangle {
|
|||
if (result.status !== "success") {
|
||||
errorText.text = "There was a problem while retrieving your inventory. " +
|
||||
"Please try closing and re-opening the Avatar app.\n\nInventory status: " + result.status + "\nMessage: " + result.message;
|
||||
} else if (result.data && result.data.assets && result.data.assets.length === 0) {
|
||||
} else if (result.data && result.data.assets && result.data.assets.length === 0 && avatarAppInventoryModel.count === 0) {
|
||||
errorText.text = "You have not created any avatars yet! Create an avatar with the Avatar Creator, then close and re-open the Avatar App."
|
||||
}
|
||||
|
||||
|
|
|
@ -69,8 +69,8 @@ Flickable {
|
|||
SimplifiedControls.Slider {
|
||||
id: peopleVolume
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 30
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
height: 30
|
||||
labelText: "People Volume"
|
||||
from: simplifiedUI.numericConstants.mutedValue
|
||||
to: 20.0
|
||||
|
@ -96,8 +96,8 @@ Flickable {
|
|||
SimplifiedControls.Slider {
|
||||
id: environmentVolume
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 30
|
||||
Layout.topMargin: 2
|
||||
height: 30
|
||||
labelText: "Environment Volume"
|
||||
from: simplifiedUI.numericConstants.mutedValue
|
||||
to: 20.0
|
||||
|
@ -124,8 +124,8 @@ Flickable {
|
|||
SimplifiedControls.Slider {
|
||||
id: systemSoundVolume
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 30
|
||||
Layout.topMargin: 2
|
||||
height: 30
|
||||
labelText: "System Sound Volume"
|
||||
from: simplifiedUI.numericConstants.mutedValue
|
||||
to: 20.0
|
||||
|
@ -169,8 +169,8 @@ Flickable {
|
|||
|
||||
SimplifiedControls.Switch {
|
||||
id: muteMicrophoneSwitch
|
||||
width: parent.width
|
||||
height: 18
|
||||
Layout.preferredHeight: 18
|
||||
Layout.preferredWidth: parent.width
|
||||
labelTextOn: "Mute Microphone"
|
||||
checked: AudioScriptingInterface.mutedDesktop
|
||||
onClicked: {
|
||||
|
@ -180,8 +180,8 @@ Flickable {
|
|||
|
||||
SimplifiedControls.Switch {
|
||||
id: pushToTalkSwitch
|
||||
width: parent.width
|
||||
height: 18
|
||||
Layout.preferredHeight: 18
|
||||
Layout.preferredWidth: parent.width
|
||||
labelTextOn: "Push to Talk - Press and Hold \"T\" to Talk"
|
||||
checked: AudioScriptingInterface.pushToTalkDesktop
|
||||
onClicked: {
|
||||
|
@ -210,9 +210,9 @@ Flickable {
|
|||
ListView {
|
||||
id: inputDeviceListView
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
height: contentItem.height
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.input
|
||||
|
@ -305,9 +305,9 @@ Flickable {
|
|||
ListView {
|
||||
id: outputDeviceListView
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
height: contentItem.height
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.output
|
||||
|
|
|
@ -199,9 +199,9 @@ Flickable {
|
|||
ListView {
|
||||
id: inputDeviceListView
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
height: contentItem.height
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.input
|
||||
|
@ -294,9 +294,9 @@ Flickable {
|
|||
ListView {
|
||||
id: outputDeviceListView
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
height: contentItem.height
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.output
|
||||
|
|
|
@ -3800,10 +3800,14 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (_firstRun.get()) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
_firstRun.set(false);
|
||||
|
||||
if (!_overrideEntry) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
sentTo = SENT_TO_PREVIOUS_LOCATION;
|
||||
}
|
||||
_firstRun.set(false);
|
||||
} else {
|
||||
QString goingTo = "";
|
||||
if (addressLookupString.isEmpty()) {
|
||||
|
@ -3819,7 +3823,7 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
sentTo = SENT_TO_PREVIOUS_LOCATION;
|
||||
}
|
||||
|
||||
|
||||
UserActivityLogger::getInstance().logAction("startup_sent_to", {
|
||||
{ "sent_to", sentTo },
|
||||
{ "sandbox_is_running", sandboxIsRunning },
|
||||
|
@ -9354,6 +9358,19 @@ void Application::showUrlHandler(const QUrl& url) {
|
|||
}
|
||||
});
|
||||
}
|
||||
void Application::overrideEntry(){
|
||||
_overrideEntry = true;
|
||||
}
|
||||
void Application::forceDisplayName(const QString& displayName) {
|
||||
getMyAvatar()->setDisplayName(displayName);
|
||||
}
|
||||
void Application::forceLoginWithTokens(const QString& tokens) {
|
||||
DependencyManager::get<AccountManager>()->setAccessTokens(tokens);
|
||||
Setting::Handle<bool>(KEEP_ME_LOGGED_IN_SETTING_NAME, true).set(true);
|
||||
}
|
||||
void Application::setConfigFileURL(const QString& fileUrl) {
|
||||
DependencyManager::get<AccountManager>()->setConfigFileURL(fileUrl);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void Application::beforeEnterBackground() {
|
||||
|
|
|
@ -356,6 +356,11 @@ public:
|
|||
|
||||
void openDirectory(const QString& path);
|
||||
|
||||
void overrideEntry();
|
||||
void forceDisplayName(const QString& displayName);
|
||||
void forceLoginWithTokens(const QString& tokens);
|
||||
void setConfigFileURL(const QString& fileUrl);
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
@ -828,5 +833,6 @@ private:
|
|||
bool _resumeAfterLoginDialogActionTaken_WasPostponed { false };
|
||||
bool _resumeAfterLoginDialogActionTaken_SafeToRun { false };
|
||||
bool _startUpFinished { false };
|
||||
bool _overrideEntry { false };
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -83,6 +83,8 @@ int main(int argc, const char* argv[]) {
|
|||
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
||||
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
|
||||
QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts <path>", "path");
|
||||
QCommandLineOption responseTokensOption("tokens", "set response tokens <json>", "json");
|
||||
QCommandLineOption displayNameOption("displayName", "set user display name <string>", "string");
|
||||
|
||||
parser.addOption(urlOption);
|
||||
parser.addOption(noLauncherOption);
|
||||
|
@ -93,6 +95,8 @@ int main(int argc, const char* argv[]) {
|
|||
parser.addOption(overrideAppLocalDataPathOption);
|
||||
parser.addOption(overrideScriptsPathOption);
|
||||
parser.addOption(allowMultipleInstancesOption);
|
||||
parser.addOption(responseTokensOption);
|
||||
parser.addOption(displayNameOption);
|
||||
|
||||
if (!parser.parse(arguments)) {
|
||||
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
|
||||
|
@ -120,8 +124,10 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
static const QString APPLICATION_CONFIG_FILENAME = "config.json";
|
||||
QDir applicationDir(applicationPath);
|
||||
QFile configFile(applicationDir.filePath(APPLICATION_CONFIG_FILENAME));
|
||||
|
||||
QString configFileName = applicationDir.filePath(APPLICATION_CONFIG_FILENAME);
|
||||
QFile configFile(configFileName);
|
||||
QString launcherPath;
|
||||
|
||||
if (configFile.exists()) {
|
||||
if (!configFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Found application config, but could not open it";
|
||||
|
@ -134,7 +140,7 @@ int main(int argc, const char* argv[]) {
|
|||
qWarning() << "Found application config, but could not parse it: " << error.errorString();
|
||||
} else {
|
||||
static const QString LAUNCHER_PATH_KEY = "launcherPath";
|
||||
QString launcherPath = doc.object()[LAUNCHER_PATH_KEY].toString();
|
||||
launcherPath = doc.object()[LAUNCHER_PATH_KEY].toString();
|
||||
if (!launcherPath.isEmpty()) {
|
||||
if (!parser.isSet(noLauncherOption)) {
|
||||
qDebug() << "Found a launcherPath in application config. Starting launcher.";
|
||||
|
@ -146,6 +152,7 @@ int main(int argc, const char* argv[]) {
|
|||
qDebug() << "Found a launcherPath in application config, but the launcher"
|
||||
" has been suppressed. Continuing normal execution.";
|
||||
}
|
||||
configFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -398,6 +405,24 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
printSystemInformation();
|
||||
|
||||
auto appPointer = dynamic_cast<Application*>(&app);
|
||||
if (appPointer) {
|
||||
if (parser.isSet(urlOption)) {
|
||||
appPointer->overrideEntry();
|
||||
}
|
||||
if (parser.isSet(displayNameOption)) {
|
||||
QString displayName = QString(parser.value(displayNameOption));
|
||||
appPointer->forceDisplayName(displayName);
|
||||
}
|
||||
if (!launcherPath.isEmpty()) {
|
||||
appPointer->setConfigFileURL(configFileName);
|
||||
}
|
||||
if (parser.isSet(responseTokensOption)) {
|
||||
QString tokens = QString(parser.value(responseTokensOption));
|
||||
appPointer->forceLoginWithTokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
QTranslator translator;
|
||||
translator.load("i18n/interface_en");
|
||||
app.installTranslator(&translator);
|
||||
|
|
|
@ -220,6 +220,10 @@ void GL45Texture::generateMips() const {
|
|||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
// (NOTE: it seems to work now, but for posterity:) DSA ARB does not work on AMD, so use EXT
|
||||
// unless EXT is not available on the driver
|
||||
#define AMD_CUBE_MAP_EXT_WORKAROUND 0
|
||||
|
||||
Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
|
||||
Size amountCopied = sourceSize;
|
||||
if (GL_TEXTURE_2D == _target) {
|
||||
|
@ -267,22 +271,26 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
|
|||
case GL_COMPRESSED_SIGNED_R11_EAC:
|
||||
case GL_COMPRESSED_RG11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
||||
#if AMD_CUBE_MAP_EXT_WORKAROUND
|
||||
if (glCompressedTextureSubImage2DEXT) {
|
||||
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
|
||||
glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat,
|
||||
static_cast<GLsizei>(sourceSize), sourcePointer);
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
glCompressedTextureSubImage3D(_id, mip, 0, yOffset, face, size.x, size.y, 1, internalFormat,
|
||||
static_cast<GLsizei>(sourceSize), sourcePointer);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// DSA ARB does not work on AMD, so use EXT
|
||||
// unless EXT is not available on the driver
|
||||
#if AMD_CUBE_MAP_EXT_WORKAROUND
|
||||
if (glTextureSubImage2DEXT) {
|
||||
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
|
||||
glTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
glTextureSubImage3D(_id, mip, 0, yOffset, face, size.x, size.y, 1, format, type, sourcePointer);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -26,8 +26,8 @@ const Element Element::COLOR_COMPRESSED_BCX_SRGB { TILE4x4, COMPRESSED, COMPRESS
|
|||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_HDR_RGB { TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA };
|
||||
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_RGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB };
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_SRGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB };
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "Batch.h"
|
||||
#include "TextureTable.h"
|
||||
|
||||
|
||||
#include "FrameIOKeys.h"
|
||||
|
||||
namespace gpu {
|
||||
|
@ -324,6 +323,13 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
|
|||
readOptional(ktxFile, node, keys::ktxFile);
|
||||
Element ktxTexelFormat, ktxMipFormat;
|
||||
if (!ktxFile.empty()) {
|
||||
// If we get a texture that starts with ":" we need to re-route it to the resources directory
|
||||
if (ktxFile.at(0) == ':') {
|
||||
QString frameReaderPath = __FILE__;
|
||||
frameReaderPath.replace("\\", "/");
|
||||
frameReaderPath.replace("libraries/gpu/src/gpu/framereader.cpp", "interface/resources", Qt::CaseInsensitive);
|
||||
ktxFile.replace(0, 1, frameReaderPath.toStdString());
|
||||
}
|
||||
if (QFileInfo(ktxFile.c_str()).isRelative()) {
|
||||
ktxFile = basedir + ktxFile;
|
||||
}
|
||||
|
|
|
@ -26,13 +26,6 @@ layout(location=0) in vec3 _normal;
|
|||
layout(location=0) out vec4 _fragColor;
|
||||
|
||||
void main(void) {
|
||||
vec3 coord = normalize(_normal);
|
||||
vec3 color = skybox.color.rgb;
|
||||
|
||||
// blend is only set if there is a cubemap
|
||||
float check = float(skybox.color.a > 0.0);
|
||||
color = mix(color, texture(cubeMap, coord).rgb, check);
|
||||
color *= mix(vec3(1.0), skybox.color.rgb, check * float(skybox.color.a < 1.0));
|
||||
|
||||
_fragColor = vec4(color, 0.0);
|
||||
vec3 skyboxColor = texture(cubeMap, normalize(_normal)).rgb;
|
||||
_fragColor = vec4(mix(skybox.color.rgb, skyboxColor, skybox.color.a), 1.0);
|
||||
}
|
||||
|
|
|
@ -690,6 +690,8 @@ void convertImageToLDRTexture(gpu::Texture* texture, Image&& image, BackendTarge
|
|||
compressionOptions.setFormat(nvtt::Format_BC4);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) {
|
||||
compressionOptions.setFormat(nvtt::Format_BC5);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
|
||||
compressionOptions.setFormat(nvtt::Format_BC6);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) {
|
||||
alphaMode = nvtt::AlphaMode_Transparency;
|
||||
compressionOptions.setFormat(nvtt::Format_BC7);
|
||||
|
|
|
@ -57,6 +57,7 @@ namespace TextureUsage {
|
|||
* @typedef {number} TextureCache.TextureType
|
||||
*/
|
||||
enum Type {
|
||||
// NOTE: add new texture types at the bottom here
|
||||
DEFAULT_TEXTURE,
|
||||
STRICT_TEXTURE,
|
||||
ALBEDO_TEXTURE,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QJsonObject>
|
||||
|
||||
const QString TEXTURE_META_EXTENSION = ".texmeta.json";
|
||||
const uint16_t KTX_VERSION = 1;
|
||||
|
||||
bool TextureMeta::deserialize(const QByteArray& data, TextureMeta* meta) {
|
||||
QJsonParseError error;
|
||||
|
@ -46,6 +47,9 @@ bool TextureMeta::deserialize(const QByteArray& data, TextureMeta* meta) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (root.contains("version")) {
|
||||
meta->version = root["version"].toInt();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -62,6 +66,7 @@ QByteArray TextureMeta::serialize() {
|
|||
root["original"] = original.toString();
|
||||
root["uncompressed"] = uncompressed.toString();
|
||||
root["compressed"] = compressed;
|
||||
root["version"] = KTX_VERSION;
|
||||
doc.setObject(root);
|
||||
|
||||
return doc.toJson();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "khronos/KHR.h"
|
||||
|
||||
extern const QString TEXTURE_META_EXTENSION;
|
||||
extern const uint16_t KTX_VERSION;
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<khronos::gl::texture::InternalFormat> {
|
||||
|
@ -37,6 +38,7 @@ struct TextureMeta {
|
|||
QUrl original;
|
||||
QUrl uncompressed;
|
||||
std::unordered_map<khronos::gl::texture::InternalFormat, QUrl> availableTextureTypes;
|
||||
uint16_t version { 0 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ void AccountManager::logout() {
|
|||
|
||||
// remove this account from the account settings file
|
||||
removeAccountFromFile();
|
||||
saveLoginStatus(false);
|
||||
|
||||
emit logoutComplete();
|
||||
// the username has changed to blank
|
||||
|
@ -650,6 +651,39 @@ void AccountManager::refreshAccessToken() {
|
|||
}
|
||||
}
|
||||
|
||||
void AccountManager::setAccessTokens(const QString& response) {
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(response.toUtf8());
|
||||
const QJsonObject& rootObject = jsonResponse.object();
|
||||
|
||||
if (!rootObject.contains("error")) {
|
||||
// construct an OAuthAccessToken from the json object
|
||||
|
||||
if (!rootObject.contains("access_token") || !rootObject.contains("expires_in")
|
||||
|| !rootObject.contains("token_type")) {
|
||||
// TODO: error handling - malformed token response
|
||||
qCDebug(networking) << "Received a response for password grant that is missing one or more expected values.";
|
||||
} else {
|
||||
// clear the path from the response URL so we have the right root URL for this access token
|
||||
QUrl rootURL = rootObject.contains("url") ? rootObject["url"].toString() : _authURL;
|
||||
rootURL.setPath("");
|
||||
|
||||
qCDebug(networking) << "Storing an account with access-token for" << qPrintable(rootURL.toString());
|
||||
|
||||
_accountInfo = DataServerAccountInfo();
|
||||
_accountInfo.setAccessTokenFromJSON(rootObject);
|
||||
emit loginComplete(rootURL);
|
||||
|
||||
persistAccountToFile();
|
||||
saveLoginStatus(true);
|
||||
requestProfile();
|
||||
}
|
||||
} else {
|
||||
// TODO: error handling
|
||||
qCDebug(networking) << "Error in response for password grant -" << rootObject["error_description"].toString();
|
||||
emit loginFailed();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountManager::requestAccessTokenFinished() {
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
|
@ -895,3 +929,34 @@ void AccountManager::handleKeypairGenerationError() {
|
|||
void AccountManager::setLimitedCommerce(bool isLimited) {
|
||||
_limitedCommerce = isLimited;
|
||||
}
|
||||
|
||||
void AccountManager::saveLoginStatus(bool isLoggedIn) {
|
||||
if (!_configFileURL.isEmpty()) {
|
||||
QFile configFile(_configFileURL);
|
||||
configFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(configFile.readAll(), &error);
|
||||
configFile.close();
|
||||
QString launcherPath;
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
QJsonObject rootObject = jsonDocument.object();
|
||||
if (rootObject.contains("launcherPath")) {
|
||||
launcherPath = rootObject["launcherPath"].toString();
|
||||
}
|
||||
if (rootObject.contains("loggedIn")) {
|
||||
rootObject["loggedIn"] = isLoggedIn;
|
||||
}
|
||||
jsonDocument = QJsonDocument(rootObject);
|
||||
|
||||
}
|
||||
configFile.open(QFile::WriteOnly | QFile::Text | QFile::Truncate);
|
||||
configFile.write(jsonDocument.toJson());
|
||||
configFile.close();
|
||||
if (!isLoggedIn && !launcherPath.isEmpty()) {
|
||||
QProcess launcher;
|
||||
launcher.setProgram(launcherPath);
|
||||
launcher.startDetached();
|
||||
qApp->quit();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,6 +102,10 @@ public:
|
|||
bool getLimitedCommerce() { return _limitedCommerce; }
|
||||
void setLimitedCommerce(bool isLimited);
|
||||
|
||||
void setAccessTokens(const QString& response);
|
||||
void setConfigFileURL(const QString& fileURL) { _configFileURL = fileURL; }
|
||||
void saveLoginStatus(bool isLoggedIn);
|
||||
|
||||
public slots:
|
||||
void requestAccessToken(const QString& login, const QString& password);
|
||||
void requestAccessTokenWithSteam(QByteArray authSessionTicket);
|
||||
|
@ -162,6 +166,7 @@ private:
|
|||
QUuid _sessionID { QUuid::createUuid() };
|
||||
|
||||
bool _limitedCommerce { false };
|
||||
QString _configFileURL;
|
||||
};
|
||||
|
||||
#endif // hifi_AccountManager_h
|
||||
|
|
|
@ -42,5 +42,5 @@ void main(void) {
|
|||
color = max(color, vec3(0));
|
||||
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
|
||||
color = pow(color, vec3(2.2));
|
||||
_fragColor = vec4(color, 0.0);
|
||||
_fragColor = vec4(color, 1.0);
|
||||
}
|
||||
|
|
|
@ -648,6 +648,7 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) {
|
|||
|
||||
if (!_defaultLight || !_defaultBackground) {
|
||||
auto defaultSkyboxURL = PathUtils::resourcesUrl() + "images/Default-Sky-9-cubemap/Default-Sky-9-cubemap.texmeta.json";
|
||||
auto defaultAmbientURL = PathUtils::resourcesUrl() + "images/Default-Sky-9-cubemap/Default-Sky-9-cubemap-ambient.texmeta.json";
|
||||
|
||||
if (!_defaultSkyboxNetworkTexture) {
|
||||
PROFILE_RANGE(render, "Process Default Skybox");
|
||||
|
@ -658,7 +659,7 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) {
|
|||
if (!_defaultAmbientNetworkTexture) {
|
||||
PROFILE_RANGE(render, "Process Default Ambient map");
|
||||
_defaultAmbientNetworkTexture = DependencyManager::get<TextureCache>()->getTexture(
|
||||
defaultSkyboxURL, image::TextureUsage::AMBIENT_TEXTURE);
|
||||
defaultAmbientURL, image::TextureUsage::AMBIENT_TEXTURE);
|
||||
}
|
||||
|
||||
if (_defaultSkyboxNetworkTexture && _defaultSkyboxNetworkTexture->isLoaded() && _defaultSkyboxNetworkTexture->getGPUTexture()) {
|
||||
|
|
|
@ -16,7 +16,7 @@ var DEFAULT_SCRIPTS_PATH_PREFIX = ScriptDiscoveryService.defaultScriptsPath + "/
|
|||
|
||||
var DEFAULT_SCRIPTS_SEPARATE = [
|
||||
DEFAULT_SCRIPTS_PATH_PREFIX + "system/controllers/controllerScripts.js",
|
||||
Script.resolvePath("simplifiedUI.js")
|
||||
DEFAULT_SCRIPTS_PATH_PREFIX + "ui/simplifiedUI.js"
|
||||
];
|
||||
function loadSeparateDefaults() {
|
||||
for (var i in DEFAULT_SCRIPTS_SEPARATE) {
|
||||
|
|
387
scripts/simplifiedUI/modules/appUi.js
Normal file
387
scripts/simplifiedUI/modules/appUi.js
Normal file
|
@ -0,0 +1,387 @@
|
|||
"use strict";
|
||||
/* global Tablet, Script */
|
||||
//
|
||||
// libraries/appUi.js
|
||||
//
|
||||
// Created by Howard Stearns on 3/20/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
function AppUi(properties) {
|
||||
var request = Script.require('request').request;
|
||||
/* Example development order:
|
||||
1. var AppUi = Script.require('appUi');
|
||||
2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3).
|
||||
3. ui = new AppUi({buttonName: "APPNAME", home: "qml-or-html-path"});
|
||||
(And if converting an existing app,
|
||||
define var tablet = ui.tablet, button = ui.button; as needed.
|
||||
remove button.clicked.[dis]connect and tablet.remove(button).)
|
||||
4. Define onOpened and onClosed behavior in #3, if any.
|
||||
(And if converting an existing app, remove screenChanged.[dis]connect.)
|
||||
5. Define onMessage and sendMessage in #3, if any. onMessage is wired/unwired on open/close. If you
|
||||
want a handler to be "always on", connect it yourself at script startup.
|
||||
(And if converting an existing app, remove code that [un]wires that message handling such as
|
||||
fromQml/sendToQml or webEventReceived/emitScriptEvent.)
|
||||
6. (If converting an existing app, cleanup stuff that is no longer necessary, like references to button, tablet,
|
||||
and use isOpen, open(), and close() as needed.)
|
||||
7. lint!
|
||||
*/
|
||||
var that = this;
|
||||
function defaultButton(name, suffix) {
|
||||
var base = that[name] || (that.buttonPrefix + suffix);
|
||||
that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); // poor man's merge
|
||||
}
|
||||
|
||||
// Defaults:
|
||||
that.tabletName = "com.highfidelity.interface.tablet.system";
|
||||
that.inject = "";
|
||||
that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below.
|
||||
that.additionalAppScreens = [];
|
||||
that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen.
|
||||
// Actual url may have prefix or suffix.
|
||||
return that.currentVisibleUrl &&
|
||||
((that.home.indexOf(that.currentVisibleUrl) > -1) ||
|
||||
(that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1));
|
||||
};
|
||||
that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) {
|
||||
that.currentVisibleScreenType = type;
|
||||
that.currentVisibleUrl = url;
|
||||
};
|
||||
that.open = function open(optionalUrl, optionalInject) { // How to open the app.
|
||||
var url = optionalUrl || that.home;
|
||||
var inject = optionalInject || that.inject;
|
||||
|
||||
if (that.isQMLUrl(url)) {
|
||||
that.tablet.loadQMLSource(url);
|
||||
} else {
|
||||
that.tablet.gotoWebScreen(url, inject);
|
||||
}
|
||||
};
|
||||
// Opens some app on top of the current app (on desktop, opens new window)
|
||||
that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) {
|
||||
var inject = optionalInject || "";
|
||||
if (that.isQMLUrl(url)) {
|
||||
that.tablet.loadQMLOnTop(url);
|
||||
} else {
|
||||
that.tablet.loadWebScreenOnTop(url, inject);
|
||||
}
|
||||
};
|
||||
that.close = function close() { // How to close the app.
|
||||
that.currentVisibleUrl = "";
|
||||
// for toolbar-mode: go back to home screen, this will close the window.
|
||||
that.tablet.gotoHomeScreen();
|
||||
};
|
||||
that.buttonActive = function buttonActive(isActive) { // How to make the button active (white).
|
||||
that.button.editProperties({isActive: isActive});
|
||||
};
|
||||
that.isQMLUrl = function isQMLUrl(url) {
|
||||
var type = /.qml$/.test(url) ? 'QML' : 'Web';
|
||||
return type === 'QML';
|
||||
};
|
||||
that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() {
|
||||
return that.currentVisibleScreenType === 'QML';
|
||||
};
|
||||
|
||||
//
|
||||
// START Notification Handling Defaults
|
||||
//
|
||||
that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button.
|
||||
// Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true.
|
||||
that.button.editProperties({
|
||||
icon: isWaiting ? that.normalMessagesButton : that.normalButton,
|
||||
activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
|
||||
});
|
||||
};
|
||||
that.notificationPollTimeout = [false];
|
||||
that.notificationPollTimeoutMs = [60000];
|
||||
that.notificationPollEndpoint = [false];
|
||||
that.notificationPollStopPaginatingConditionMet = [false];
|
||||
that.notificationDataProcessPage = function (data) {
|
||||
return data;
|
||||
};
|
||||
that.notificationPollCallback = [that.ignore];
|
||||
that.notificationPollCaresAboutSince = [false];
|
||||
that.notificationInitialCallbackMade = [false];
|
||||
that.notificationDisplayBanner = function (message) {
|
||||
if (!that.isOpen) {
|
||||
Window.displayAnnouncement(message);
|
||||
}
|
||||
};
|
||||
//
|
||||
// END Notification Handling Defaults
|
||||
//
|
||||
|
||||
// Handlers
|
||||
that.onScreenChanged = function onScreenChanged(type, url) {
|
||||
// Set isOpen, wireEventBridge, set buttonActive as appropriate,
|
||||
// and finally call onOpened() or onClosed() IFF defined.
|
||||
that.setCurrentVisibleScreenMetadata(type, url);
|
||||
|
||||
if (that.checkIsOpen(type, url)) {
|
||||
that.wireEventBridge(true);
|
||||
if (!that.isOpen) {
|
||||
that.buttonActive(true);
|
||||
if (that.onOpened) {
|
||||
that.onOpened();
|
||||
}
|
||||
that.isOpen = true;
|
||||
}
|
||||
} else {
|
||||
// A different screen is now visible, or the tablet has been closed.
|
||||
// Tablet visibility is controlled separately by `tabletShownChanged()`
|
||||
that.wireEventBridge(false);
|
||||
if (that.isOpen) {
|
||||
that.buttonActive(false);
|
||||
if (that.onClosed) {
|
||||
that.onClosed();
|
||||
}
|
||||
that.isOpen = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Overwrite with the given properties:
|
||||
Object.keys(properties).forEach(function (key) {
|
||||
that[key] = properties[key];
|
||||
});
|
||||
|
||||
//
|
||||
// START Notification Handling
|
||||
//
|
||||
|
||||
var currentDataPageToRetrieve = [];
|
||||
var concatenatedServerResponse = [];
|
||||
for (var i = 0; i < that.notificationPollEndpoint.length; i++) {
|
||||
currentDataPageToRetrieve[i] = 1;
|
||||
concatenatedServerResponse[i] = new Array();
|
||||
}
|
||||
|
||||
var MAX_LOG_LENGTH_CHARACTERS = 300;
|
||||
function requestCallback(error, response, optionalParams) {
|
||||
var indexOfRequest = optionalParams.indexOfRequest;
|
||||
var urlOfRequest = optionalParams.urlOfRequest;
|
||||
|
||||
if (error || (response.status !== 'success')) {
|
||||
print("Error: unable to complete request from URL. Error:", error || response.status);
|
||||
startNotificationTimer(indexOfRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!that.notificationPollStopPaginatingConditionMet[indexOfRequest] ||
|
||||
that.notificationPollStopPaginatingConditionMet[indexOfRequest](response)) {
|
||||
startNotificationTimer(indexOfRequest);
|
||||
|
||||
var notificationData;
|
||||
if (concatenatedServerResponse[indexOfRequest].length) {
|
||||
notificationData = concatenatedServerResponse[indexOfRequest];
|
||||
} else {
|
||||
notificationData = that.notificationDataProcessPage[indexOfRequest](response);
|
||||
}
|
||||
console.debug(that.buttonName,
|
||||
'truncated notification data for processing:',
|
||||
JSON.stringify(notificationData).substring(0, MAX_LOG_LENGTH_CHARACTERS));
|
||||
that.notificationPollCallback[indexOfRequest](notificationData);
|
||||
that.notificationInitialCallbackMade[indexOfRequest] = true;
|
||||
currentDataPageToRetrieve[indexOfRequest] = 1;
|
||||
concatenatedServerResponse[indexOfRequest] = new Array();
|
||||
} else {
|
||||
concatenatedServerResponse[indexOfRequest] =
|
||||
concatenatedServerResponse[indexOfRequest].concat(that.notificationDataProcessPage[indexOfRequest](response));
|
||||
currentDataPageToRetrieve[indexOfRequest]++;
|
||||
request({
|
||||
json: true,
|
||||
uri: (urlOfRequest + "&page=" + currentDataPageToRetrieve[indexOfRequest])
|
||||
}, requestCallback, optionalParams);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var METAVERSE_BASE = Account.metaverseServerURL;
|
||||
var MS_IN_SEC = 1000;
|
||||
that.notificationPoll = function (i) {
|
||||
if (!that.notificationPollEndpoint[i]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// User is "appearing offline" or is not logged in
|
||||
if (GlobalServices.findableBy === "none" || Account.username === "Unknown user") {
|
||||
// The notification polling will restart when the user changes their availability
|
||||
// or when they log in, so it's not necessary to restart a timer here.
|
||||
console.debug(that.buttonName + " Notifications: User is appearing offline or not logged in. " +
|
||||
that.buttonName + " will poll for notifications when user logs in and has their availability " +
|
||||
"set to not appear offline.");
|
||||
return;
|
||||
}
|
||||
|
||||
var url = METAVERSE_BASE + that.notificationPollEndpoint[i];
|
||||
|
||||
var settingsKey = "notifications/" + that.notificationPollEndpoint[i] + "/lastPoll";
|
||||
var currentTimestamp = new Date().getTime();
|
||||
var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp);
|
||||
if (that.notificationPollCaresAboutSince[i]) {
|
||||
url = url + "&since=" + lastPollTimestamp / MS_IN_SEC;
|
||||
}
|
||||
Settings.setValue(settingsKey, currentTimestamp);
|
||||
|
||||
request({
|
||||
json: true,
|
||||
uri: url
|
||||
},
|
||||
requestCallback,
|
||||
{
|
||||
indexOfRequest: i,
|
||||
urlOfRequest: url
|
||||
});
|
||||
};
|
||||
|
||||
// This won't do anything if there isn't a notification endpoint set
|
||||
for (i = 0; i < that.notificationPollEndpoint.length; i++) {
|
||||
that.notificationPoll(i);
|
||||
}
|
||||
|
||||
function startNotificationTimer(indexOfRequest) {
|
||||
that.notificationPollTimeout[indexOfRequest] = Script.setTimeout(function () {
|
||||
that.notificationPoll(indexOfRequest);
|
||||
}, that.notificationPollTimeoutMs[indexOfRequest]);
|
||||
}
|
||||
|
||||
function restartNotificationPoll() {
|
||||
for (var j = 0; j < that.notificationPollEndpoint.length; j++) {
|
||||
that.notificationInitialCallbackMade[j] = false;
|
||||
if (that.notificationPollTimeout[j]) {
|
||||
Script.clearTimeout(that.notificationPollTimeout[j]);
|
||||
that.notificationPollTimeout[j] = false;
|
||||
}
|
||||
that.notificationPoll(j);
|
||||
}
|
||||
}
|
||||
//
|
||||
// END Notification Handling
|
||||
//
|
||||
|
||||
// Properties:
|
||||
that.tablet = Tablet.getTablet(that.tabletName);
|
||||
// Must be after we gather properties.
|
||||
that.buttonPrefix = that.buttonPrefix || that.buttonName.toLowerCase() + "-";
|
||||
defaultButton('normalButton', 'i.svg');
|
||||
defaultButton('activeButton', 'a.svg');
|
||||
defaultButton('normalMessagesButton', 'i-msg.svg');
|
||||
defaultButton('activeMessagesButton', 'a-msg.svg');
|
||||
var buttonOptions = {
|
||||
icon: that.normalButton,
|
||||
activeIcon: that.activeButton,
|
||||
text: that.buttonName
|
||||
};
|
||||
// `TabletScriptingInterface` looks for the presence of a `sortOrder` key.
|
||||
// What it SHOULD do is look to see if the value inside that key is defined.
|
||||
// To get around the current code, we do this instead.
|
||||
if (that.sortOrder) {
|
||||
buttonOptions.sortOrder = that.sortOrder;
|
||||
}
|
||||
that.button = that.tablet.addButton(buttonOptions);
|
||||
that.ignore = function ignore() { };
|
||||
that.hasOutboundEventBridge = false;
|
||||
that.hasInboundQmlEventBridge = false;
|
||||
that.hasInboundHtmlEventBridge = false;
|
||||
// HTML event bridge uses strings, not objects. Here we abstract over that.
|
||||
// (Although injected javascript still has to use JSON.stringify/JSON.parse.)
|
||||
that.sendToHtml = function (messageObject) {
|
||||
that.tablet.emitScriptEvent(JSON.stringify(messageObject));
|
||||
};
|
||||
that.fromHtml = function (messageString) {
|
||||
var parsedMessage = JSON.parse(messageString);
|
||||
parsedMessage.messageSrc = "HTML";
|
||||
that.onMessage(parsedMessage);
|
||||
};
|
||||
that.sendMessage = that.ignore;
|
||||
that.wireEventBridge = function wireEventBridge(on) {
|
||||
// Uniquivocally sets that.sendMessage(messageObject) to do the right thing.
|
||||
// Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined.
|
||||
var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen();
|
||||
// Outbound (always, regardless of whether there is an inbound handler).
|
||||
if (on) {
|
||||
that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml;
|
||||
that.hasOutboundEventBridge = true;
|
||||
} else {
|
||||
that.sendMessage = that.ignore;
|
||||
that.hasOutboundEventBridge = false;
|
||||
}
|
||||
|
||||
if (!that.onMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inbound
|
||||
if (on) {
|
||||
if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) {
|
||||
console.debug(that.buttonName, 'connecting', that.tablet.fromQml);
|
||||
that.tablet.fromQml.connect(that.onMessage);
|
||||
that.hasInboundQmlEventBridge = true;
|
||||
} else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) {
|
||||
console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived);
|
||||
that.tablet.webEventReceived.connect(that.fromHtml);
|
||||
that.hasInboundHtmlEventBridge = true;
|
||||
}
|
||||
} else {
|
||||
if (that.hasInboundQmlEventBridge) {
|
||||
console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml);
|
||||
that.tablet.fromQml.disconnect(that.onMessage);
|
||||
that.hasInboundQmlEventBridge = false;
|
||||
}
|
||||
if (that.hasInboundHtmlEventBridge) {
|
||||
console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived);
|
||||
that.tablet.webEventReceived.disconnect(that.fromHtml);
|
||||
that.hasInboundHtmlEventBridge = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
that.isOpen = false;
|
||||
// To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties.
|
||||
that.onClicked = that.home
|
||||
? function onClicked() {
|
||||
// Call open() or close(), and reset type based on current home property.
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
} else {
|
||||
that.open();
|
||||
}
|
||||
} : that.ignore;
|
||||
that.onScriptEnding = function onScriptEnding() {
|
||||
// Close if necessary, clean up any remaining handlers, and remove the button.
|
||||
GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll);
|
||||
GlobalServices.findableByChanged.disconnect(restartNotificationPoll);
|
||||
that.tablet.screenChanged.disconnect(that.onScreenChanged);
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
that.onScreenChanged("", "");
|
||||
}
|
||||
if (that.button) {
|
||||
if (that.onClicked) {
|
||||
that.button.clicked.disconnect(that.onClicked);
|
||||
}
|
||||
that.tablet.removeButton(that.button);
|
||||
}
|
||||
for (var i = 0; i < that.notificationPollTimeout.length; i++) {
|
||||
if (that.notificationPollTimeout[i]) {
|
||||
Script.clearInterval(that.notificationPollTimeout[i]);
|
||||
that.notificationPollTimeout[i] = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Set up the handlers.
|
||||
that.tablet.screenChanged.connect(that.onScreenChanged);
|
||||
that.button.clicked.connect(that.onClicked);
|
||||
Script.scriptEnding.connect(that.onScriptEnding);
|
||||
GlobalServices.findableByChanged.connect(restartNotificationPoll);
|
||||
GlobalServices.myUsernameChanged.connect(restartNotificationPoll);
|
||||
if (that.buttonName === Settings.getValue("startUpApp")) {
|
||||
Settings.setValue("startUpApp", "");
|
||||
Script.setTimeout(function () {
|
||||
that.open();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
module.exports = AppUi;
|
83
scripts/simplifiedUI/modules/request.js
Normal file
83
scripts/simplifiedUI/modules/request.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
"use strict";
|
||||
|
||||
// request.js
|
||||
//
|
||||
// Created by Cisco Fresquet on 04/24/2017.
|
||||
// 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 module */
|
||||
// @module request
|
||||
//
|
||||
// This module contains the `request` module implementation
|
||||
|
||||
// ===========================================================================================
|
||||
module.exports = {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// cb(error, responseOfCorrectContentType, optionalCallbackParameter) of url. A subset of npm request.
|
||||
request: function (options, callback, optionalCallbackParameter) {
|
||||
var httpRequest = new XMLHttpRequest(), key;
|
||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||
httpRequest.onreadystatechange = function () {
|
||||
var READY_STATE_DONE = 4;
|
||||
var HTTP_OK = 200;
|
||||
if (httpRequest.readyState >= READY_STATE_DONE) {
|
||||
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
|
||||
response = !error && httpRequest.responseText,
|
||||
contentType = !error && httpRequest.getResponseHeader('content-type');
|
||||
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
response = { statusCode: httpRequest.status };
|
||||
}
|
||||
callback(error, response, optionalCallbackParameter);
|
||||
}
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
options = { uri: options };
|
||||
}
|
||||
if (options.url) {
|
||||
options.uri = options.url;
|
||||
}
|
||||
if (!options.method) {
|
||||
options.method = 'GET';
|
||||
}
|
||||
if (options.body && (options.method === 'GET')) { // add query parameters
|
||||
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
|
||||
for (key in options.body) {
|
||||
if (options.body.hasOwnProperty(key)) {
|
||||
params.push(key + '=' + options.body[key]);
|
||||
}
|
||||
}
|
||||
options.uri += appender + params.join('&');
|
||||
delete options.body;
|
||||
}
|
||||
if (options.json) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers["Content-type"] = "application/json";
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
for (key in options.headers || {}) {
|
||||
if (options.headers.hasOwnProperty(key)) {
|
||||
httpRequest.setRequestHeader(key, options.headers[key]);
|
||||
}
|
||||
}
|
||||
httpRequest.open(options.method, options.uri, true);
|
||||
httpRequest.send(options.body || null);
|
||||
}
|
||||
};
|
||||
|
||||
// ===========================================================================================
|
||||
// @function - debug logging
|
||||
function debug() {
|
||||
print('RequestModule | ' + [].slice.call(arguments).join(' '));
|
||||
}
|
69
scripts/simplifiedUI/modules/vec3.js
Normal file
69
scripts/simplifiedUI/modules/vec3.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Example of using a "system module" to decouple Vec3's implementation details.
|
||||
//
|
||||
// Users would bring Vec3 support in as a module:
|
||||
// var vec3 = Script.require('vec3');
|
||||
//
|
||||
|
||||
// (this example is compatible with using as a Script.include and as a Script.require module)
|
||||
try {
|
||||
// Script.require
|
||||
module.exports = vec3;
|
||||
} catch(e) {
|
||||
// Script.include
|
||||
Script.registerValue("vec3", vec3);
|
||||
}
|
||||
|
||||
vec3.fromObject = function(v) {
|
||||
//return new vec3(v.x, v.y, v.z);
|
||||
//... this is even faster and achieves the same effect
|
||||
v.__proto__ = vec3.prototype;
|
||||
return v;
|
||||
};
|
||||
|
||||
vec3.prototype = {
|
||||
multiply: function(v2) {
|
||||
// later on could support overrides like so:
|
||||
// if (v2 instanceof quat) { [...] }
|
||||
// which of the below is faster (C++ or JS)?
|
||||
// (dunno -- but could systematically find out and go with that version)
|
||||
|
||||
// pure JS option
|
||||
// return new vec3(this.x * v2.x, this.y * v2.y, this.z * v2.z);
|
||||
|
||||
// hybrid C++ option
|
||||
return vec3.fromObject(Vec3.multiply(this, v2));
|
||||
},
|
||||
// detects any NaN and Infinity values
|
||||
isValid: function() {
|
||||
return isFinite(this.x) && isFinite(this.y) && isFinite(this.z);
|
||||
},
|
||||
// format Vec3's, eg:
|
||||
// var v = vec3();
|
||||
// print(v); // outputs [Vec3 (0.000, 0.000, 0.000)]
|
||||
toString: function() {
|
||||
if (this === vec3.prototype) {
|
||||
return "{Vec3 prototype}";
|
||||
}
|
||||
function fixed(n) { return n.toFixed(3); }
|
||||
return "[Vec3 (" + [this.x, this.y, this.z].map(fixed) + ")]";
|
||||
},
|
||||
};
|
||||
|
||||
vec3.DEBUG = true;
|
||||
|
||||
function vec3(x, y, z) {
|
||||
if (!(this instanceof vec3)) {
|
||||
// if vec3 is called as a function then re-invoke as a constructor
|
||||
// (so that `value instanceof vec3` holds true for created values)
|
||||
return new vec3(x, y, z);
|
||||
}
|
||||
|
||||
// unfold default arguments (vec3(), vec3(.5), vec3(0,1), etc.)
|
||||
this.x = x !== undefined ? x : 0;
|
||||
this.y = y !== undefined ? y : this.x;
|
||||
this.z = z !== undefined ? z : this.y;
|
||||
|
||||
if (vec3.DEBUG && !this.isValid())
|
||||
throw new Error('vec3() -- invalid initial values ['+[].slice.call(arguments)+']');
|
||||
};
|
||||
|
387
scripts/simplifiedUI/system/away.js
Normal file
387
scripts/simplifiedUI/system/away.js
Normal file
|
@ -0,0 +1,387 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// away.js
|
||||
//
|
||||
// examples
|
||||
//
|
||||
// Created by Howard Stearns 11/3/15
|
||||
// Copyright 2015 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
|
||||
//
|
||||
// Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key.
|
||||
// See MAIN CONTROL, below, for what "paused" actually does.
|
||||
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var BASIC_TIMER_INTERVAL = 50; // 50ms = 20hz
|
||||
var OVERLAY_WIDTH = 1920;
|
||||
var OVERLAY_HEIGHT = 1080;
|
||||
var OVERLAY_DATA = {
|
||||
width: OVERLAY_WIDTH,
|
||||
height: OVERLAY_HEIGHT,
|
||||
imageURL: Script.resolvePath("assets/images/Overlay-Viz-blank.png"),
|
||||
emissive: true,
|
||||
drawInFront: true,
|
||||
alpha: 1
|
||||
};
|
||||
var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away
|
||||
|
||||
var CAMERA_MATRIX = -7;
|
||||
|
||||
var OVERLAY_DATA_HMD = {
|
||||
localPosition: {x: 0, y: 0, z: -1 * MyAvatar.sensorToWorldScale},
|
||||
localRotation: {x: 0, y: 0, z: 0, w: 1},
|
||||
width: OVERLAY_WIDTH,
|
||||
height: OVERLAY_HEIGHT,
|
||||
url: Script.resolvePath("assets/images/Overlay-Viz-blank.png"),
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
alpha: 1,
|
||||
scale: 2 * MyAvatar.sensorToWorldScale,
|
||||
emissive: true,
|
||||
drawInFront: true,
|
||||
parentID: MyAvatar.SELF_ID,
|
||||
parentJointIndex: CAMERA_MATRIX,
|
||||
ignorePickIntersection: true
|
||||
};
|
||||
|
||||
var AWAY_INTRO = {
|
||||
url: "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx",
|
||||
playbackRate: 30.0,
|
||||
loopFlag: false,
|
||||
startFrame: 0.0,
|
||||
endFrame: 83.0
|
||||
};
|
||||
|
||||
// MAIN CONTROL
|
||||
var isEnabled = true;
|
||||
var wasMuted; // unknonwn?
|
||||
var isAway = false; // we start in the un-away state
|
||||
var eventMappingName = "io.highfidelity.away"; // goActive on hand controller button events, too.
|
||||
var eventMapping = Controller.newMapping(eventMappingName);
|
||||
var avatarPosition = MyAvatar.position;
|
||||
var wasHmdMounted = HMD.mounted;
|
||||
var previousBubbleState = Users.getIgnoreRadiusEnabled();
|
||||
|
||||
var enterAwayStateWhenFocusLostInVR = HMD.enterAwayStateWhenFocusLostInVR;
|
||||
|
||||
// some intervals we may create/delete
|
||||
var avatarMovedInterval;
|
||||
|
||||
|
||||
// prefetch the kneel animation and hold a ref so it's always resident in memory when we need it.
|
||||
var _animation = AnimationCache.prefetch(AWAY_INTRO.url);
|
||||
|
||||
function playAwayAnimation() {
|
||||
MyAvatar.overrideAnimation(AWAY_INTRO.url,
|
||||
AWAY_INTRO.playbackRate,
|
||||
AWAY_INTRO.loopFlag,
|
||||
AWAY_INTRO.startFrame,
|
||||
AWAY_INTRO.endFrame);
|
||||
}
|
||||
|
||||
function stopAwayAnimation() {
|
||||
MyAvatar.restoreAnimation();
|
||||
}
|
||||
|
||||
// OVERLAY
|
||||
var overlay = Overlays.addOverlay("image", OVERLAY_DATA);
|
||||
var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD);
|
||||
|
||||
function showOverlay() {
|
||||
if (HMD.active) {
|
||||
// make sure desktop version is hidden
|
||||
Overlays.editOverlay(overlay, { visible: false });
|
||||
Overlays.editOverlay(overlayHMD, { visible: true });
|
||||
} else {
|
||||
// make sure HMD is hidden
|
||||
Overlays.editOverlay(overlayHMD, { visible: false });
|
||||
|
||||
// Update for current screen size, keeping overlay proportions constant.
|
||||
var screen = Controller.getViewportDimensions();
|
||||
|
||||
// keep the overlay it's natural size and always center it...
|
||||
Overlays.editOverlay(overlay, {
|
||||
visible: true,
|
||||
x: ((screen.x - OVERLAY_WIDTH) / 2),
|
||||
y: ((screen.y - OVERLAY_HEIGHT) / 2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
Overlays.editOverlay(overlay, {visible: false});
|
||||
Overlays.editOverlay(overlayHMD, {visible: false});
|
||||
}
|
||||
|
||||
hideOverlay();
|
||||
|
||||
function maybeMoveOverlay() {
|
||||
if (isAway) {
|
||||
// if we switched from HMD to Desktop, make sure to hide our HUD overlay and show the
|
||||
// desktop overlay
|
||||
if (!HMD.active) {
|
||||
showOverlay(); // this will also recenter appropriately
|
||||
}
|
||||
|
||||
if (HMD.active) {
|
||||
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
var localPosition = {x: 0, y: 0, z: -1 * sensorScaleFactor};
|
||||
Overlays.editOverlay(overlayHMD, { visible: true, localPosition: localPosition, scale: 2 * sensorScaleFactor });
|
||||
|
||||
// make sure desktop version is hidden
|
||||
Overlays.editOverlay(overlay, { visible: false });
|
||||
|
||||
// also remember avatar position
|
||||
avatarPosition = MyAvatar.position;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ifAvatarMovedGoActive() {
|
||||
var newAvatarPosition = MyAvatar.position;
|
||||
if (Vec3.distance(newAvatarPosition, avatarPosition) > AVATAR_MOVE_FOR_ACTIVE_DISTANCE) {
|
||||
goActive();
|
||||
}
|
||||
avatarPosition = newAvatarPosition;
|
||||
}
|
||||
|
||||
function goAway(fromStartup) {
|
||||
if (!isEnabled || isAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're entering away mode from some other state than startup, then we create our move timer immediately.
|
||||
// However if we're just stating up, we need to delay this process so that we don't think the initial teleport
|
||||
// is actually a move.
|
||||
if (fromStartup === undefined || fromStartup === false) {
|
||||
avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL);
|
||||
} else {
|
||||
var WAIT_FOR_MOVE_ON_STARTUP = 3000; // 3 seconds
|
||||
Script.setTimeout(function() {
|
||||
avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL);
|
||||
}, WAIT_FOR_MOVE_ON_STARTUP);
|
||||
}
|
||||
|
||||
previousBubbleState = Users.getIgnoreRadiusEnabled();
|
||||
if (!previousBubbleState) {
|
||||
Users.toggleIgnoreRadius();
|
||||
}
|
||||
UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled());
|
||||
UserActivityLogger.toggledAway(true);
|
||||
MyAvatar.isAway = true;
|
||||
}
|
||||
|
||||
function goActive() {
|
||||
if (!isAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserActivityLogger.toggledAway(false);
|
||||
MyAvatar.isAway = false;
|
||||
|
||||
if (Users.getIgnoreRadiusEnabled() !== previousBubbleState) {
|
||||
Users.toggleIgnoreRadius();
|
||||
UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled());
|
||||
}
|
||||
|
||||
if (!Window.hasFocus()) {
|
||||
Window.setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
MyAvatar.wentAway.connect(setAwayProperties);
|
||||
MyAvatar.wentActive.connect(setActiveProperties);
|
||||
|
||||
function setAwayProperties() {
|
||||
isAway = true;
|
||||
wasMuted = Audio.muted;
|
||||
if (!wasMuted) {
|
||||
Audio.muted = !Audio.muted;
|
||||
}
|
||||
MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view
|
||||
playAwayAnimation(); // animation is still seen by others
|
||||
showOverlay();
|
||||
|
||||
HMD.requestShowHandControllers();
|
||||
|
||||
// tell the Reticle, we want to stop capturing the mouse until we come back
|
||||
Reticle.allowMouseCapture = false;
|
||||
// Allow users to find their way to other applications, our menus, etc.
|
||||
// For desktop, that means we want the reticle visible.
|
||||
// For HMD, the hmd preview will show the system mouse because of allowMouseCapture,
|
||||
// but we want to turn off our Reticle so that we don't get two in preview and a stuck one in headset.
|
||||
Reticle.visible = !HMD.active;
|
||||
wasHmdMounted = HMD.mounted; // always remember the correct state
|
||||
|
||||
avatarPosition = MyAvatar.position;
|
||||
}
|
||||
|
||||
function setActiveProperties() {
|
||||
isAway = false;
|
||||
if (Audio.muted && !wasMuted) {
|
||||
Audio.muted = false;
|
||||
}
|
||||
MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting.
|
||||
stopAwayAnimation();
|
||||
|
||||
HMD.requestHideHandControllers();
|
||||
|
||||
// update the UI sphere to be centered about the current HMD orientation.
|
||||
HMD.centerUI();
|
||||
|
||||
// forget about any IK joint limits
|
||||
MyAvatar.clearIKJointLimitHistory();
|
||||
|
||||
// update the avatar hips to point in the same direction as the HMD orientation.
|
||||
MyAvatar.centerBody();
|
||||
|
||||
hideOverlay();
|
||||
|
||||
// tell the Reticle, we are ready to capture the mouse again and it should be visible
|
||||
Reticle.allowMouseCapture = true;
|
||||
Reticle.visible = true;
|
||||
if (HMD.active) {
|
||||
Reticle.position = HMD.getHUDLookAtPosition2D();
|
||||
}
|
||||
wasHmdMounted = HMD.mounted; // always remember the correct state
|
||||
|
||||
Script.clearInterval(avatarMovedInterval);
|
||||
}
|
||||
|
||||
function maybeGoActive(event) {
|
||||
if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it)
|
||||
return;
|
||||
}
|
||||
if (!isAway && (event.text === 'ESC')) {
|
||||
goAway();
|
||||
} else {
|
||||
goActive();
|
||||
}
|
||||
}
|
||||
|
||||
var wasHmdActive = HMD.active;
|
||||
var wasMouseCaptured = Reticle.mouseCaptured;
|
||||
|
||||
function maybeGoAway() {
|
||||
// If our active state change (went to or from HMD mode), and we are now in the HMD, go into away
|
||||
if (HMD.active !== wasHmdActive) {
|
||||
wasHmdActive = !wasHmdActive;
|
||||
if (wasHmdActive) {
|
||||
goAway();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD,
|
||||
// but tabbed away from the application (meaning they don't have mouse control) and they likely want to go into
|
||||
// an away state
|
||||
if (Reticle.mouseCaptured !== wasMouseCaptured) {
|
||||
wasMouseCaptured = !wasMouseCaptured;
|
||||
if (!wasMouseCaptured) {
|
||||
if (enterAwayStateWhenFocusLostInVR) {
|
||||
goAway();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If you've removed your HMD from your head, and we can detect it, we will also go away...
|
||||
if (HMD.mounted !== wasHmdMounted) {
|
||||
wasHmdMounted = HMD.mounted;
|
||||
print("HMD mounted changed...");
|
||||
|
||||
// We're putting the HMD on... switch to those devices
|
||||
if (HMD.mounted) {
|
||||
print("NOW mounted...");
|
||||
} else {
|
||||
print("HMD NOW un-mounted...");
|
||||
|
||||
if (HMD.active) {
|
||||
goAway();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setEnabled(value) {
|
||||
if (!value) {
|
||||
goActive();
|
||||
}
|
||||
isEnabled = value;
|
||||
}
|
||||
|
||||
function checkAudioToggled() {
|
||||
if (isAway && !Audio.muted) {
|
||||
goActive();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable";
|
||||
var handleMessage = function(channel, message, sender) {
|
||||
if (channel === CHANNEL_AWAY_ENABLE && sender === MyAvatar.sessionUUID) {
|
||||
print("away.js | Got message on Hifi-Away-Enable: ", message);
|
||||
setEnabled(message === 'enable');
|
||||
}
|
||||
};
|
||||
Messages.subscribe(CHANNEL_AWAY_ENABLE);
|
||||
Messages.messageReceived.connect(handleMessage);
|
||||
|
||||
var maybeIntervalTimer = Script.setInterval(function() {
|
||||
maybeMoveOverlay();
|
||||
maybeGoAway();
|
||||
checkAudioToggled();
|
||||
}, BASIC_TIMER_INTERVAL);
|
||||
|
||||
|
||||
Controller.mousePressEvent.connect(goActive);
|
||||
Controller.keyPressEvent.connect(maybeGoActive);
|
||||
// Note peek() so as to not interfere with other mappings.
|
||||
eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LT).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LB).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LS).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LeftGrip).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RT).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RB).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RS).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RightGrip).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.Back).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.Start).peek().to(goActive);
|
||||
Controller.enableMapping(eventMappingName);
|
||||
|
||||
function awayStateWhenFocusLostInVRChanged(enabled) {
|
||||
enterAwayStateWhenFocusLostInVR = enabled;
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
Script.clearInterval(maybeIntervalTimer);
|
||||
goActive();
|
||||
HMD.awayStateWhenFocusLostInVRChanged.disconnect(awayStateWhenFocusLostInVRChanged);
|
||||
Controller.disableMapping(eventMappingName);
|
||||
Controller.mousePressEvent.disconnect(goActive);
|
||||
Controller.keyPressEvent.disconnect(maybeGoActive);
|
||||
Messages.messageReceived.disconnect(handleMessage);
|
||||
Messages.unsubscribe(CHANNEL_AWAY_ENABLE);
|
||||
});
|
||||
|
||||
HMD.awayStateWhenFocusLostInVRChanged.connect(awayStateWhenFocusLostInVRChanged);
|
||||
|
||||
if (HMD.active && !HMD.mounted) {
|
||||
print("Starting script, while HMD is active and not mounted...");
|
||||
goAway(true);
|
||||
}
|
||||
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
|
||||
// controllerScripts.js
|
||||
//
|
||||
// Created by David Rowe on 15 Mar 2017.
|
||||
// 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, Menu */
|
||||
|
||||
var CONTOLLER_SCRIPTS = [
|
||||
"squeezeHands.js",
|
||||
"controllerDisplayManager.js",
|
||||
"toggleAdvancedMovementForHandControllers.js",
|
||||
"controllerDispatcher.js",
|
||||
"controllerModules/nearParentGrabOverlay.js",
|
||||
"controllerModules/stylusInput.js",
|
||||
"controllerModules/equipEntity.js",
|
||||
"controllerModules/nearTrigger.js",
|
||||
"controllerModules/webSurfaceLaserInput.js",
|
||||
"controllerModules/inVREditMode.js",
|
||||
"controllerModules/disableOtherModule.js",
|
||||
"controllerModules/farTrigger.js",
|
||||
"controllerModules/teleport.js",
|
||||
"controllerModules/hudOverlayPointer.js",
|
||||
"controllerModules/scaleEntity.js",
|
||||
"controllerModules/nearGrabHyperLinkEntity.js",
|
||||
"controllerModules/nearTabletHighlight.js",
|
||||
"controllerModules/nearGrabEntity.js",
|
||||
"controllerModules/farGrabEntity.js"
|
||||
];
|
||||
|
||||
var DEBUG_MENU_ITEM = "Debug defaultScripts.js";
|
||||
|
||||
function runDefaultsTogether() {
|
||||
for (var j in CONTOLLER_SCRIPTS) {
|
||||
if (CONTOLLER_SCRIPTS.hasOwnProperty(j)) {
|
||||
Script.include(CONTOLLER_SCRIPTS[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runDefaultsSeparately() {
|
||||
for (var i in CONTOLLER_SCRIPTS) {
|
||||
if (CONTOLLER_SCRIPTS.hasOwnProperty(i)) {
|
||||
Script.load(CONTOLLER_SCRIPTS[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Menu.isOptionChecked(DEBUG_MENU_ITEM)) {
|
||||
runDefaultsSeparately();
|
||||
} else {
|
||||
runDefaultsTogether();
|
||||
}
|
615
scripts/simplifiedUI/system/controllers/controllerDispatcher.js
Normal file
615
scripts/simplifiedUI/system/controllers/controllerDispatcher.js
Normal file
|
@ -0,0 +1,615 @@
|
|||
"use strict";
|
||||
|
||||
// controllerDispatcher.js
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* jslint bitwise: true */
|
||||
|
||||
/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation,
|
||||
controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true,
|
||||
LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES,
|
||||
getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers,
|
||||
PointerManager, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers,
|
||||
PointerManager, print, Keyboard
|
||||
*/
|
||||
|
||||
controllerDispatcherPlugins = {};
|
||||
controllerDispatcherPluginsNeedSort = false;
|
||||
|
||||
Script.include("/~/system/libraries/utils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
(function() {
|
||||
Script.include("/~/system/libraries/pointersUtils.js");
|
||||
|
||||
var NEAR_MAX_RADIUS = 0.1;
|
||||
var NEAR_TABLET_MAX_RADIUS = 0.05;
|
||||
|
||||
var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update
|
||||
var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ;
|
||||
|
||||
var PROFILE = false;
|
||||
var DEBUG = false;
|
||||
var SHOW_GRAB_SPHERE = false;
|
||||
|
||||
|
||||
if (typeof Test !== "undefined") {
|
||||
PROFILE = true;
|
||||
}
|
||||
|
||||
function ControllerDispatcher() {
|
||||
var _this = this;
|
||||
this.lastInterval = Date.now();
|
||||
this.intervalCount = 0;
|
||||
this.totalDelta = 0;
|
||||
this.totalVariance = 0;
|
||||
this.highVarianceCount = 0;
|
||||
this.veryhighVarianceCount = 0;
|
||||
this.orderedPluginNames = [];
|
||||
this.tabletID = null;
|
||||
this.blacklist = [];
|
||||
this.pointerManager = new PointerManager();
|
||||
this.grabSphereOverlays = [null, null];
|
||||
this.targetIDs = {};
|
||||
|
||||
// a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are
|
||||
// not set to false (not in use), a module cannot start. When a module is using a slot, that module's name
|
||||
// is stored as the value, rather than false.
|
||||
this.activitySlots = {
|
||||
head: false,
|
||||
leftHand: false,
|
||||
rightHand: false,
|
||||
rightHandTrigger: false,
|
||||
leftHandTrigger: false,
|
||||
rightHandEquip: false,
|
||||
leftHandEquip: false,
|
||||
mouse: false
|
||||
};
|
||||
|
||||
this.laserVisibleStatus = [false, false, false, false];
|
||||
this.laserLockStatus = [false, false, false, false];
|
||||
|
||||
this.slotsAreAvailableForPlugin = function (plugin) {
|
||||
for (var i = 0; i < plugin.parameters.activitySlots.length; i++) {
|
||||
if (_this.activitySlots[plugin.parameters.activitySlots[i]]) {
|
||||
return false; // something is already using a slot which _this plugin requires
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
this.markSlots = function (plugin, pluginName) {
|
||||
for (var i = 0; i < plugin.parameters.activitySlots.length; i++) {
|
||||
_this.activitySlots[plugin.parameters.activitySlots[i]] = pluginName;
|
||||
}
|
||||
};
|
||||
|
||||
this.unmarkSlotsForPluginName = function (runningPluginName) {
|
||||
// this is used to free activity-slots when a plugin is deactivated while it's running.
|
||||
for (var activitySlot in _this.activitySlots) {
|
||||
if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] === runningPluginName) {
|
||||
_this.activitySlots[activitySlot] = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.runningPluginNames = {};
|
||||
this.leftTriggerValue = 0;
|
||||
this.leftTriggerClicked = 0;
|
||||
this.rightTriggerValue = 0;
|
||||
this.rightTriggerClicked = 0;
|
||||
this.leftSecondaryValue = 0;
|
||||
this.rightSecondaryValue = 0;
|
||||
|
||||
this.leftTriggerPress = function (value) {
|
||||
_this.leftTriggerValue = value;
|
||||
};
|
||||
this.leftTriggerClick = function (value) {
|
||||
_this.leftTriggerClicked = value;
|
||||
};
|
||||
this.rightTriggerPress = function (value) {
|
||||
_this.rightTriggerValue = value;
|
||||
};
|
||||
this.rightTriggerClick = function (value) {
|
||||
_this.rightTriggerClicked = value;
|
||||
};
|
||||
this.leftSecondaryPress = function (value) {
|
||||
_this.leftSecondaryValue = value;
|
||||
};
|
||||
this.rightSecondaryPress = function (value) {
|
||||
_this.rightSecondaryValue = value;
|
||||
};
|
||||
|
||||
this.dataGatherers = {};
|
||||
this.dataGatherers.leftControllerLocation = function () {
|
||||
return getControllerWorldLocation(Controller.Standard.LeftHand, true);
|
||||
};
|
||||
this.dataGatherers.rightControllerLocation = function () {
|
||||
return getControllerWorldLocation(Controller.Standard.RightHand, true);
|
||||
};
|
||||
|
||||
this.updateTimings = function () {
|
||||
_this.intervalCount++;
|
||||
var thisInterval = Date.now();
|
||||
var deltaTimeMsec = thisInterval - _this.lastInterval;
|
||||
var deltaTime = deltaTimeMsec / 1000;
|
||||
_this.lastInterval = thisInterval;
|
||||
|
||||
_this.totalDelta += deltaTimeMsec;
|
||||
|
||||
var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS);
|
||||
_this.totalVariance += variance;
|
||||
|
||||
if (variance > 1) {
|
||||
_this.highVarianceCount++;
|
||||
}
|
||||
|
||||
if (variance > 5) {
|
||||
_this.veryhighVarianceCount++;
|
||||
}
|
||||
|
||||
return deltaTime;
|
||||
};
|
||||
|
||||
this.setIgnorePointerItems = function() {
|
||||
if (HMD.tabletID && HMD.tabletID !== this.tabletID) {
|
||||
this.tabletID = HMD.tabletID;
|
||||
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist);
|
||||
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist);
|
||||
}
|
||||
};
|
||||
|
||||
this.update = function () {
|
||||
try {
|
||||
_this.updateInternal();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS);
|
||||
};
|
||||
|
||||
this.updateInternal = function () {
|
||||
if (PROFILE) {
|
||||
Script.beginProfileRange("dispatch.pre");
|
||||
}
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
var deltaTime = _this.updateTimings();
|
||||
_this.setIgnorePointerItems();
|
||||
|
||||
if (controllerDispatcherPluginsNeedSort) {
|
||||
_this.orderedPluginNames = [];
|
||||
for (var pluginName in controllerDispatcherPlugins) {
|
||||
if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) {
|
||||
_this.orderedPluginNames.push(pluginName);
|
||||
}
|
||||
}
|
||||
_this.orderedPluginNames.sort(function (a, b) {
|
||||
return controllerDispatcherPlugins[a].parameters.priority -
|
||||
controllerDispatcherPlugins[b].parameters.priority;
|
||||
});
|
||||
|
||||
controllerDispatcherPluginsNeedSort = false;
|
||||
}
|
||||
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.pre");
|
||||
}
|
||||
|
||||
if (PROFILE) {
|
||||
Script.beginProfileRange("dispatch.gather");
|
||||
}
|
||||
|
||||
var controllerLocations = [
|
||||
_this.dataGatherers.leftControllerLocation(),
|
||||
_this.dataGatherers.rightControllerLocation()
|
||||
];
|
||||
|
||||
// find 3d overlays near each hand
|
||||
var nearbyOverlayIDs = [];
|
||||
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);
|
||||
|
||||
// Tablet and mini-tablet must be within NEAR_TABLET_MAX_RADIUS in order to be grabbed.
|
||||
// Mini tablet can only be grabbed the hand it's displayed on.
|
||||
var tabletIndex = nearbyOverlays.indexOf(HMD.tabletID);
|
||||
var miniTabletIndex = nearbyOverlays.indexOf(HMD.miniTabletID);
|
||||
if (tabletIndex !== -1 || miniTabletIndex !== -1) {
|
||||
var closebyOverlays =
|
||||
Overlays.findOverlays(controllerLocations[h].position, NEAR_TABLET_MAX_RADIUS * sensorScaleFactor);
|
||||
// Assumes that the tablet and mini-tablet are not displayed at the same time.
|
||||
if (tabletIndex !== -1 && closebyOverlays.indexOf(HMD.tabletID) === -1) {
|
||||
nearbyOverlays.splice(tabletIndex, 1);
|
||||
}
|
||||
if (miniTabletIndex !== -1 &&
|
||||
((closebyOverlays.indexOf(HMD.miniTabletID) === -1) || h !== HMD.miniTabletHand)) {
|
||||
nearbyOverlays.splice(miniTabletIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
nearbyOverlays.sort(function (a, b) {
|
||||
var aPosition = Overlays.getProperty(a, "position");
|
||||
var aDistance = Vec3.distance(aPosition, controllerLocations[h].position);
|
||||
var bPosition = Overlays.getProperty(b, "position");
|
||||
var bDistance = Vec3.distance(bPosition, controllerLocations[h].position);
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
|
||||
nearbyOverlayIDs.push(nearbyOverlays);
|
||||
} else {
|
||||
nearbyOverlayIDs.push([]);
|
||||
}
|
||||
}
|
||||
|
||||
// find entities near each hand
|
||||
var nearbyEntityProperties = [[], []];
|
||||
var nearbyEntityPropertiesByID = {};
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
if (controllerLocations[h].valid) {
|
||||
var controllerPosition = controllerLocations[h].position;
|
||||
var findRadius = NEAR_MAX_RADIUS * sensorScaleFactor;
|
||||
|
||||
if (SHOW_GRAB_SPHERE) {
|
||||
if (this.grabSphereOverlays[h]) {
|
||||
Overlays.editOverlay(this.grabSphereOverlays[h], { position: controllerLocations[h].position });
|
||||
} else {
|
||||
var grabSphereSize = findRadius * 2;
|
||||
this.grabSphereOverlays[h] = Overlays.addOverlay("sphere", {
|
||||
position: controllerLocations[h].position,
|
||||
dimensions: { x: grabSphereSize, y: grabSphereSize, z: grabSphereSize },
|
||||
color: { red: 30, green: 30, blue: 255 },
|
||||
alpha: 0.3,
|
||||
solid: true,
|
||||
visible: true,
|
||||
// lineWidth: 2.0,
|
||||
drawInFront: false,
|
||||
grabbable: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var nearbyEntityIDs = Entities.findEntities(controllerPosition, findRadius);
|
||||
for (var j = 0; j < nearbyEntityIDs.length; j++) {
|
||||
var entityID = nearbyEntityIDs[j];
|
||||
var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
||||
props.id = entityID;
|
||||
props.distance = Vec3.distance(props.position, controllerLocations[h].position);
|
||||
nearbyEntityPropertiesByID[entityID] = props;
|
||||
nearbyEntityProperties[h].push(props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// raypick for each controller
|
||||
var rayPicks = [
|
||||
Pointers.getPrevPickResult(_this.leftPointer),
|
||||
Pointers.getPrevPickResult(_this.rightPointer)
|
||||
];
|
||||
var hudRayPicks = [
|
||||
Pointers.getPrevPickResult(_this.leftHudPointer),
|
||||
Pointers.getPrevPickResult(_this.rightHudPointer)
|
||||
];
|
||||
var mouseRayPick = Pointers.getPrevPickResult(_this.mouseRayPick);
|
||||
// if the pickray hit something very nearby, put it into the nearby entities list
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
|
||||
// XXX find a way to extract searchRay from samuel's stuff
|
||||
rayPicks[h].searchRay = {
|
||||
origin: controllerLocations[h].position,
|
||||
direction: Quat.getUp(controllerLocations[h].orientation),
|
||||
length: 1000
|
||||
};
|
||||
|
||||
if (rayPicks[h].type === Picks.INTERSECTED_ENTITY) {
|
||||
// XXX check to make sure this one isn't already in nearbyEntityProperties?
|
||||
if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS * sensorScaleFactor) {
|
||||
var nearEntityID = rayPicks[h].objectID;
|
||||
var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES);
|
||||
nearbyProps.id = nearEntityID;
|
||||
nearbyProps.distance = rayPicks[h].distance;
|
||||
nearbyEntityPropertiesByID[nearEntityID] = nearbyProps;
|
||||
nearbyEntityProperties[h].push(nearbyProps);
|
||||
}
|
||||
}
|
||||
|
||||
// sort by distance from each hand
|
||||
nearbyEntityProperties[h].sort(function (a, b) {
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
// also make sure we have the properties from the current module's target
|
||||
for (var tIDRunningPluginName in _this.runningPluginNames) {
|
||||
if (_this.runningPluginNames.hasOwnProperty(tIDRunningPluginName)) {
|
||||
var targetIDs = _this.targetIDs[tIDRunningPluginName];
|
||||
if (targetIDs) {
|
||||
for (var k = 0; k < targetIDs.length; k++) {
|
||||
var targetID = targetIDs[k];
|
||||
if (!nearbyEntityPropertiesByID[targetID]) {
|
||||
var targetProps = Entities.getEntityProperties(targetID, DISPATCHER_PROPERTIES);
|
||||
targetProps.id = targetID;
|
||||
nearbyEntityPropertiesByID[targetID] = targetProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bundle up all the data about the current situation
|
||||
var controllerData = {
|
||||
triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue],
|
||||
triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked],
|
||||
secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue],
|
||||
controllerLocations: controllerLocations,
|
||||
nearbyEntityProperties: nearbyEntityProperties,
|
||||
nearbyEntityPropertiesByID: nearbyEntityPropertiesByID,
|
||||
nearbyOverlayIDs: nearbyOverlayIDs,
|
||||
rayPicks: rayPicks,
|
||||
hudRayPicks: hudRayPicks,
|
||||
mouseRayPick: mouseRayPick
|
||||
};
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.gather");
|
||||
}
|
||||
|
||||
if (PROFILE) {
|
||||
Script.beginProfileRange("dispatch.isReady");
|
||||
}
|
||||
// check for plugins that would like to start. ask in order of increasing priority value
|
||||
for (var pluginIndex = 0; pluginIndex < _this.orderedPluginNames.length; pluginIndex++) {
|
||||
var orderedPluginName = _this.orderedPluginNames[pluginIndex];
|
||||
var candidatePlugin = controllerDispatcherPlugins[orderedPluginName];
|
||||
|
||||
if (_this.slotsAreAvailableForPlugin(candidatePlugin)) {
|
||||
if (PROFILE) {
|
||||
Script.beginProfileRange("dispatch.isReady." + orderedPluginName);
|
||||
}
|
||||
var readiness = candidatePlugin.isReady(controllerData, deltaTime);
|
||||
if (readiness.active) {
|
||||
// this plugin will start. add it to the list of running plugins and mark the
|
||||
// activity-slots which this plugin consumes as "in use"
|
||||
_this.runningPluginNames[orderedPluginName] = true;
|
||||
_this.markSlots(candidatePlugin, orderedPluginName);
|
||||
_this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser);
|
||||
if (DEBUG) {
|
||||
print("controllerDispatcher running " + orderedPluginName);
|
||||
}
|
||||
}
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.isReady." + orderedPluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.isReady");
|
||||
}
|
||||
|
||||
if (PROFILE) {
|
||||
Script.beginProfileRange("dispatch.run");
|
||||
}
|
||||
// give time to running plugins
|
||||
for (var runningPluginName in _this.runningPluginNames) {
|
||||
if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) {
|
||||
var plugin = controllerDispatcherPlugins[runningPluginName];
|
||||
if (!plugin) {
|
||||
// plugin was deactivated while running. find the activity-slots it was using and make
|
||||
// them available.
|
||||
delete _this.runningPluginNames[runningPluginName];
|
||||
_this.unmarkSlotsForPluginName(runningPluginName);
|
||||
} else {
|
||||
if (PROFILE) {
|
||||
Script.beginProfileRange("dispatch.run." + runningPluginName);
|
||||
}
|
||||
var runningness = plugin.run(controllerData, deltaTime);
|
||||
|
||||
if (DEBUG) {
|
||||
if (JSON.stringify(_this.targetIDs[runningPluginName]) != JSON.stringify(runningness.targets)) {
|
||||
print("controllerDispatcher targetIDs[" + runningPluginName + "] = " +
|
||||
JSON.stringify(runningness.targets));
|
||||
}
|
||||
}
|
||||
|
||||
_this.targetIDs[runningPluginName] = runningness.targets;
|
||||
if (!runningness.active) {
|
||||
// plugin is finished running, for now. remove it from the list
|
||||
// of running plugins and mark its activity-slots as "not in use"
|
||||
delete _this.runningPluginNames[runningPluginName];
|
||||
delete _this.targetIDs[runningPluginName];
|
||||
if (DEBUG) {
|
||||
print("controllerDispatcher deleted targetIDs[" + runningPluginName + "]");
|
||||
}
|
||||
_this.markSlots(plugin, false);
|
||||
_this.pointerManager.makePointerInvisible(plugin.parameters.handLaser);
|
||||
if (DEBUG) {
|
||||
print("controllerDispatcher stopping " + runningPluginName);
|
||||
}
|
||||
}
|
||||
_this.pointerManager.lockPointerEnd(plugin.parameters.handLaser, runningness.laserLockInfo);
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.run." + runningPluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues);
|
||||
if (PROFILE) {
|
||||
Script.endProfileRange("dispatch.run");
|
||||
}
|
||||
};
|
||||
|
||||
this.leftBlacklistTabletIDs = [];
|
||||
this.rightBlacklistTabletIDs = [];
|
||||
|
||||
this.setLeftBlacklist = function () {
|
||||
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat(_this.leftBlacklistTabletIDs));
|
||||
};
|
||||
this.setRightBlacklist = function () {
|
||||
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat(_this.rightBlacklistTabletIDs));
|
||||
};
|
||||
|
||||
this.setBlacklist = function() {
|
||||
_this.setLeftBlacklist();
|
||||
_this.setRightBlacklist();
|
||||
};
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.controllerDispatcher";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
mapping.from([Controller.Standard.RT]).peek().to(_this.rightTriggerPress);
|
||||
mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick);
|
||||
mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress);
|
||||
mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick);
|
||||
|
||||
mapping.from([Controller.Standard.RB]).peek().to(_this.rightSecondaryPress);
|
||||
mapping.from([Controller.Standard.LB]).peek().to(_this.leftSecondaryPress);
|
||||
mapping.from([Controller.Standard.LeftGrip]).peek().to(_this.leftSecondaryPress);
|
||||
mapping.from([Controller.Standard.RightGrip]).peek().to(_this.rightSecondaryPress);
|
||||
|
||||
Controller.enableMapping(MAPPING_NAME);
|
||||
|
||||
this.leftPointer = this.pointerManager.createPointer(false, PickType.Ray, {
|
||||
joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
|
||||
filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
triggers: [{action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"}],
|
||||
posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand, true),
|
||||
hover: true,
|
||||
scaleWithParent: true,
|
||||
distanceScaleEnd: true,
|
||||
hand: LEFT_HAND
|
||||
});
|
||||
Keyboard.setLeftHandLaser(this.leftPointer);
|
||||
this.rightPointer = this.pointerManager.createPointer(false, PickType.Ray, {
|
||||
joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
|
||||
filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
triggers: [{action: Controller.Standard.RTClick, button: "Focus"}, {action: Controller.Standard.RTClick, button: "Primary"}],
|
||||
posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand, true),
|
||||
hover: true,
|
||||
scaleWithParent: true,
|
||||
distanceScaleEnd: true,
|
||||
hand: RIGHT_HAND
|
||||
});
|
||||
Keyboard.setRightHandLaser(this.rightPointer);
|
||||
this.leftHudPointer = this.pointerManager.createPointer(true, PickType.Ray, {
|
||||
joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
|
||||
filter: Picks.PICK_HUD,
|
||||
maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE,
|
||||
posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand, true),
|
||||
triggers: [{action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"}],
|
||||
hover: true,
|
||||
scaleWithParent: true,
|
||||
distanceScaleEnd: true,
|
||||
hand: LEFT_HAND
|
||||
});
|
||||
this.rightHudPointer = this.pointerManager.createPointer(true, PickType.Ray, {
|
||||
joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
|
||||
filter: Picks.PICK_HUD,
|
||||
maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE,
|
||||
posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand, true),
|
||||
triggers: [{action: Controller.Standard.RTClick, button: "Focus"}, {action: Controller.Standard.RTClick, button: "Primary"}],
|
||||
hover: true,
|
||||
scaleWithParent: true,
|
||||
distanceScaleEnd: true,
|
||||
hand: RIGHT_HAND
|
||||
});
|
||||
|
||||
this.mouseRayPick = Pointers.createPointer(PickType.Ray, {
|
||||
joint: "Mouse",
|
||||
filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
enabled: true
|
||||
});
|
||||
this.handleMessage = function (channel, data, sender) {
|
||||
var message;
|
||||
if (sender === MyAvatar.sessionUUID) {
|
||||
try {
|
||||
if (channel === 'Hifi-Hand-RayPick-Blacklist') {
|
||||
message = JSON.parse(data);
|
||||
var action = message.action;
|
||||
var id = message.id;
|
||||
var index = _this.blacklist.indexOf(id);
|
||||
|
||||
if (action === 'add' && index === -1) {
|
||||
_this.blacklist.push(id);
|
||||
_this.setBlacklist();
|
||||
}
|
||||
|
||||
if (action === 'remove') {
|
||||
if (index > -1) {
|
||||
_this.blacklist.splice(index, 1);
|
||||
_this.setBlacklist();
|
||||
}
|
||||
}
|
||||
|
||||
if (action === "tablet") {
|
||||
var tabletIDs = message.blacklist ?
|
||||
[HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID] :
|
||||
[];
|
||||
if (message.hand === LEFT_HAND) {
|
||||
_this.leftBlacklistTabletIDs = tabletIDs;
|
||||
_this.setLeftBlacklist();
|
||||
} else {
|
||||
_this.rightBlacklistTabletIDs = tabletIDs;
|
||||
_this.setRightBlacklist();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print("WARNING: handControllerGrab.js -- error parsing message: " + data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
Controller.disableMapping(MAPPING_NAME);
|
||||
_this.pointerManager.removePointers();
|
||||
Pointers.removePointer(this.mouseRayPick);
|
||||
};
|
||||
}
|
||||
|
||||
function mouseReleaseOnOverlay(overlayID, event) {
|
||||
if (HMD.homeButtonID && overlayID === HMD.homeButtonID && event.button === "Primary") {
|
||||
Messages.sendLocalMessage("home", overlayID);
|
||||
}
|
||||
}
|
||||
|
||||
var HAPTIC_STYLUS_STRENGTH = 1.0;
|
||||
var HAPTIC_STYLUS_DURATION = 20.0;
|
||||
function mousePress(id, event) {
|
||||
if (HMD.active) {
|
||||
var runningPlugins = controllerDispatcher.runningPluginNames;
|
||||
if (event.id === controllerDispatcher.leftPointer && event.button === "Primary" && runningPlugins.LeftWebSurfaceLaserInput) {
|
||||
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, LEFT_HAND);
|
||||
} else if (event.id === controllerDispatcher.rightPointer && event.button === "Primary" && runningPlugins.RightWebSurfaceLaserInput) {
|
||||
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, RIGHT_HAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Overlays.mouseReleaseOnOverlay.connect(mouseReleaseOnOverlay);
|
||||
Overlays.mousePressOnOverlay.connect(mousePress);
|
||||
Entities.mousePressOnEntity.connect(mousePress);
|
||||
|
||||
var controllerDispatcher = new ControllerDispatcher();
|
||||
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');
|
||||
Messages.messageReceived.connect(controllerDispatcher.handleMessage);
|
||||
|
||||
Script.scriptEnding.connect(controllerDispatcher.cleanup);
|
||||
Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS);
|
||||
}());
|
292
scripts/simplifiedUI/system/controllers/controllerDisplay.js
Normal file
292
scripts/simplifiedUI/system/controllers/controllerDisplay.js
Normal file
|
@ -0,0 +1,292 @@
|
|||
//
|
||||
// controllerDisplay.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 10/20/16
|
||||
// Originally created by Ryan Huffman on 9/21/2016
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* globals createControllerDisplay:true, deleteControllerDisplay:true, Controller, Overlays, Vec3, MyAvatar, Quat */
|
||||
|
||||
function clamp(value, min, max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
} else if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function resolveHardware(path) {
|
||||
if (typeof path === 'string') {
|
||||
var parts = path.split(".");
|
||||
function resolveInner(base, path, i) {
|
||||
if (i >= path.length) {
|
||||
return base;
|
||||
}
|
||||
return resolveInner(base[path[i]], path, ++i);
|
||||
}
|
||||
return resolveInner(Controller.Hardware, parts, 0);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
var DEBUG = true;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("controllerDisplay.js | ");
|
||||
print.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
createControllerDisplay = function(config) {
|
||||
var controllerDisplay = {
|
||||
overlays: [],
|
||||
partOverlays: {},
|
||||
parts: {},
|
||||
mappingName: "mapping-display-" + Math.random(),
|
||||
partValues: {},
|
||||
|
||||
setVisible: function(visible) {
|
||||
for (var i = 0; i < this.overlays.length; ++i) {
|
||||
Overlays.editOverlay(this.overlays[i], {
|
||||
visible: visible
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setPartVisible: function(partName, visible) {
|
||||
// Disabled
|
||||
/*
|
||||
if (partName in this.partOverlays) {
|
||||
for (var i = 0; i < this.partOverlays[partName].length; ++i) {
|
||||
Overlays.editOverlay(this.partOverlays[partName][i], {
|
||||
//visible: visible
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
setLayerForPart: function(partName, layerName) {
|
||||
if (partName in this.parts) {
|
||||
var part = this.parts[partName];
|
||||
if (part.textureLayers && layerName in part.textureLayers) {
|
||||
var layer = part.textureLayers[layerName];
|
||||
var textures = {};
|
||||
if (layer.defaultTextureURL) {
|
||||
textures[part.textureName] = layer.defaultTextureURL;
|
||||
}
|
||||
for (var i = 0; i < this.partOverlays[partName].length; ++i) {
|
||||
Overlays.editOverlay(this.partOverlays[partName][i], {
|
||||
textures: textures
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
resize: function(sensorScaleFactor) {
|
||||
if (this.overlays.length >= 0) {
|
||||
var controller = config.controllers[0];
|
||||
var position = controller.position;
|
||||
|
||||
// first overlay is main body.
|
||||
var overlayID = this.overlays[0];
|
||||
var localPosition = Vec3.multiply(sensorScaleFactor, Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position));
|
||||
var dimensions = Vec3.multiply(sensorScaleFactor, controller.dimensions);
|
||||
|
||||
Overlays.editOverlay(overlayID, {
|
||||
dimensions: dimensions,
|
||||
localPosition: localPosition
|
||||
});
|
||||
|
||||
if (controller.parts) {
|
||||
var i = 1;
|
||||
for (var partName in controller.parts) {
|
||||
overlayID = this.overlays[i++];
|
||||
var part = controller.parts[partName];
|
||||
localPosition = Vec3.subtract(part.naturalPosition, controller.naturalPosition);
|
||||
var localRotation;
|
||||
var value = this.partValues[partName];
|
||||
var offset, rotation;
|
||||
if (value !== undefined) {
|
||||
if (part.type === "linear") {
|
||||
offset = Vec3.multiply(part.maxTranslation * value, part.axis);
|
||||
localPosition = Vec3.sum(localPosition, offset);
|
||||
localRotation = undefined;
|
||||
} else if (part.type === "joystick") {
|
||||
rotation = Quat.fromPitchYawRollDegrees(value.y * part.xHalfAngle, 0, value.x * part.yHalfAngle);
|
||||
if (part.originOffset) {
|
||||
offset = Vec3.multiplyQbyV(rotation, part.originOffset);
|
||||
offset = Vec3.subtract(part.originOffset, offset);
|
||||
} else {
|
||||
offset = { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
localPosition = Vec3.sum(offset, localPosition);
|
||||
localRotation = rotation;
|
||||
} else if (part.type === "rotational") {
|
||||
value = clamp(value, part.minValue, part.maxValue);
|
||||
var pct = (value - part.minValue) / part.maxValue;
|
||||
var angle = pct * part.maxAngle;
|
||||
rotation = Quat.angleAxis(angle, part.axis);
|
||||
if (part.origin) {
|
||||
offset = Vec3.multiplyQbyV(rotation, part.origin);
|
||||
offset = Vec3.subtract(offset, part.origin);
|
||||
} else {
|
||||
offset = { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
localPosition = Vec3.sum(offset, localPosition);
|
||||
localRotation = rotation;
|
||||
}
|
||||
}
|
||||
if (localRotation !== undefined) {
|
||||
Overlays.editOverlay(overlayID, {
|
||||
dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions),
|
||||
localPosition: Vec3.multiply(sensorScaleFactor, localPosition),
|
||||
localRotation: localRotation
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(overlayID, {
|
||||
dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions),
|
||||
localPosition: Vec3.multiply(sensorScaleFactor, localPosition)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var mapping = Controller.newMapping(controllerDisplay.mappingName);
|
||||
for (var i = 0; i < config.controllers.length; ++i) {
|
||||
var controller = config.controllers[i];
|
||||
var position = controller.position;
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
|
||||
if (controller.naturalPosition) {
|
||||
position = Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position);
|
||||
} else {
|
||||
controller.naturalPosition = { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
|
||||
var baseOverlayID = Overlays.addOverlay("model", {
|
||||
url: controller.modelURL,
|
||||
dimensions: Vec3.multiply(sensorScaleFactor, controller.dimensions),
|
||||
localRotation: controller.rotation,
|
||||
localPosition: Vec3.multiply(sensorScaleFactor, position),
|
||||
parentID: MyAvatar.SELF_ID,
|
||||
parentJointIndex: controller.jointIndex,
|
||||
ignoreRayIntersection: true
|
||||
});
|
||||
|
||||
controllerDisplay.overlays.push(baseOverlayID);
|
||||
|
||||
if (controller.parts) {
|
||||
for (var partName in controller.parts) {
|
||||
var part = controller.parts[partName];
|
||||
var localPosition = Vec3.subtract(part.naturalPosition, controller.naturalPosition);
|
||||
var localRotation = { x: 0, y: 0, z: 0, w: 1 };
|
||||
|
||||
controllerDisplay.parts[partName] = controller.parts[partName];
|
||||
|
||||
var properties = {
|
||||
url: part.modelURL,
|
||||
localPosition: localPosition,
|
||||
localRotation: localRotation,
|
||||
parentID: baseOverlayID,
|
||||
ignoreRayIntersection: true
|
||||
};
|
||||
|
||||
if (part.defaultTextureLayer) {
|
||||
var textures = {};
|
||||
textures[part.textureName] = part.textureLayers[part.defaultTextureLayer].defaultTextureURL;
|
||||
properties.textures = textures;
|
||||
}
|
||||
|
||||
var overlayID = Overlays.addOverlay("model", properties);
|
||||
|
||||
if (part.type === "rotational") {
|
||||
var input = resolveHardware(part.input);
|
||||
mapping.from([input]).peek().to(function(partName) {
|
||||
return function(value) {
|
||||
// insert the most recent controller value into controllerDisplay.partValues.
|
||||
controllerDisplay.partValues[partName] = value;
|
||||
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
|
||||
};
|
||||
}(partName));
|
||||
} else if (part.type === "touchpad") {
|
||||
var visibleInput = resolveHardware(part.visibleInput);
|
||||
var xInput = resolveHardware(part.xInput);
|
||||
var yInput = resolveHardware(part.yInput);
|
||||
|
||||
// TODO: Touchpad inputs are currently only working for half
|
||||
// of the touchpad. When that is fixed, it would be useful
|
||||
// to update these to display the current finger position.
|
||||
mapping.from([visibleInput]).peek().to(function(value) {
|
||||
});
|
||||
mapping.from([xInput]).peek().to(function(value) {
|
||||
});
|
||||
mapping.from([yInput]).peek().invert().to(function(value) {
|
||||
});
|
||||
} else if (part.type === "joystick") {
|
||||
(function(part, partName) {
|
||||
var xInput = resolveHardware(part.xInput);
|
||||
var yInput = resolveHardware(part.yInput);
|
||||
mapping.from([xInput]).peek().to(function(value) {
|
||||
// insert the most recent controller value into controllerDisplay.partValues.
|
||||
if (controllerDisplay.partValues[partName]) {
|
||||
controllerDisplay.partValues[partName].x = value;
|
||||
} else {
|
||||
controllerDisplay.partValues[partName] = {x: value, y: 0};
|
||||
}
|
||||
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
|
||||
});
|
||||
mapping.from([yInput]).peek().to(function(value) {
|
||||
// insert the most recent controller value into controllerDisplay.partValues.
|
||||
if (controllerDisplay.partValues[partName]) {
|
||||
controllerDisplay.partValues[partName].y = value;
|
||||
} else {
|
||||
controllerDisplay.partValues[partName] = {x: 0, y: value};
|
||||
}
|
||||
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
|
||||
});
|
||||
})(part, partName);
|
||||
|
||||
} else if (part.type === "linear") {
|
||||
(function(part, partName) {
|
||||
var input = resolveHardware(part.input);
|
||||
mapping.from([input]).peek().to(function(value) {
|
||||
// insert the most recent controller value into controllerDisplay.partValues.
|
||||
controllerDisplay.partValues[partName] = value;
|
||||
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
|
||||
});
|
||||
})(part, partName);
|
||||
|
||||
} else if (part.type === "static") {
|
||||
// do nothing
|
||||
} else {
|
||||
debug("TYPE NOT SUPPORTED: ", part.type);
|
||||
}
|
||||
|
||||
controllerDisplay.overlays.push(overlayID);
|
||||
if (!(partName in controllerDisplay.partOverlays)) {
|
||||
controllerDisplay.partOverlays[partName] = [];
|
||||
}
|
||||
controllerDisplay.partOverlays[partName].push(overlayID);
|
||||
}
|
||||
}
|
||||
}
|
||||
Controller.enableMapping(controllerDisplay.mappingName);
|
||||
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
|
||||
return controllerDisplay;
|
||||
};
|
||||
|
||||
deleteControllerDisplay = function(controllerDisplay) {
|
||||
for (var i = 0; i < controllerDisplay.overlays.length; ++i) {
|
||||
Overlays.deleteOverlay(controllerDisplay.overlays[i]);
|
||||
}
|
||||
Controller.disableMapping(controllerDisplay.mappingName);
|
||||
};
|
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// controllerDisplayManager.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 10/20/16
|
||||
// Originally created by Ryan Huffman on 9/21/2016
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* globals ControllerDisplayManager:true, createControllerDisplay, deleteControllerDisplay,
|
||||
VIVE_CONTROLLER_CONFIGURATION_LEFT, VIVE_CONTROLLER_CONFIGURATION_RIGHT, Script, HMD, Controller,
|
||||
MyAvatar, Overlays, TOUCH_CONTROLLER_CONFIGURATION_LEFT, TOUCH_CONTROLLER_CONFIGURATION_RIGHT, Messages */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function () {
|
||||
|
||||
Script.include("controllerDisplay.js");
|
||||
Script.include("viveControllerConfiguration.js");
|
||||
Script.include("touchControllerConfiguration.js");
|
||||
|
||||
var HIDE_CONTROLLERS_ON_EQUIP = false;
|
||||
|
||||
//
|
||||
// Management of controller display
|
||||
//
|
||||
ControllerDisplayManager = function() {
|
||||
var self = this;
|
||||
var controllerLeft = null;
|
||||
var controllerRight = null;
|
||||
var controllerCheckerIntervalID = null;
|
||||
|
||||
this.setLeftVisible = function(visible) {
|
||||
if (controllerLeft) {
|
||||
controllerLeft.setVisible(visible);
|
||||
}
|
||||
};
|
||||
|
||||
this.setRightVisible = function(visible) {
|
||||
if (controllerRight) {
|
||||
controllerRight.setVisible(visible);
|
||||
}
|
||||
};
|
||||
|
||||
function updateControllers() {
|
||||
if (HMD.active && HMD.shouldShowHandControllers()) {
|
||||
var leftConfig = null;
|
||||
var rightConfig = null;
|
||||
|
||||
if ("Vive" in Controller.Hardware) {
|
||||
leftConfig = VIVE_CONTROLLER_CONFIGURATION_LEFT;
|
||||
rightConfig = VIVE_CONTROLLER_CONFIGURATION_RIGHT;
|
||||
}
|
||||
|
||||
if ("OculusTouch" in Controller.Hardware) {
|
||||
leftConfig = TOUCH_CONTROLLER_CONFIGURATION_LEFT;
|
||||
rightConfig = TOUCH_CONTROLLER_CONFIGURATION_RIGHT;
|
||||
}
|
||||
|
||||
if (leftConfig !== null && rightConfig !== null) {
|
||||
if (controllerLeft === null) {
|
||||
controllerLeft = createControllerDisplay(leftConfig);
|
||||
controllerLeft.setVisible(true);
|
||||
}
|
||||
if (controllerRight === null) {
|
||||
controllerRight = createControllerDisplay(rightConfig);
|
||||
controllerRight.setVisible(true);
|
||||
}
|
||||
// We've found the controllers, we no longer need to look for active controllers
|
||||
if (controllerCheckerIntervalID) {
|
||||
Script.clearInterval(controllerCheckerIntervalID);
|
||||
controllerCheckerIntervalID = null;
|
||||
}
|
||||
|
||||
} else {
|
||||
self.deleteControllerDisplays();
|
||||
if (!controllerCheckerIntervalID) {
|
||||
controllerCheckerIntervalID = Script.setInterval(updateControllers, 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We aren't in HMD mode, we no longer need to look for active controllers
|
||||
if (controllerCheckerIntervalID) {
|
||||
Script.clearInterval(controllerCheckerIntervalID);
|
||||
controllerCheckerIntervalID = null;
|
||||
}
|
||||
self.deleteControllerDisplays();
|
||||
}
|
||||
}
|
||||
|
||||
function resizeControllers(sensorScaleFactor) {
|
||||
if (controllerLeft) {
|
||||
controllerLeft.resize(sensorScaleFactor);
|
||||
}
|
||||
if (controllerRight) {
|
||||
controllerRight.resize(sensorScaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
var handleMessages = function(channel, message, sender) {
|
||||
var i, data, name, visible;
|
||||
if (!controllerLeft && !controllerRight) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender === MyAvatar.sessionUUID) {
|
||||
if (channel === 'Controller-Display') {
|
||||
data = JSON.parse(message);
|
||||
name = data.name;
|
||||
visible = data.visible;
|
||||
if (controllerLeft) {
|
||||
if (name in controllerLeft.annotations) {
|
||||
for (i = 0; i < controllerLeft.annotations[name].length; ++i) {
|
||||
Overlays.editOverlay(controllerLeft.annotations[name][i], { visible: visible });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (controllerRight) {
|
||||
if (name in controllerRight.annotations) {
|
||||
for (i = 0; i < controllerRight.annotations[name].length; ++i) {
|
||||
Overlays.editOverlay(controllerRight.annotations[name][i], { visible: visible });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (channel === 'Controller-Display-Parts') {
|
||||
data = JSON.parse(message);
|
||||
for (name in data) {
|
||||
visible = data[name];
|
||||
if (controllerLeft) {
|
||||
controllerLeft.setPartVisible(name, visible);
|
||||
}
|
||||
if (controllerRight) {
|
||||
controllerRight.setPartVisible(name, visible);
|
||||
}
|
||||
}
|
||||
} else if (channel === 'Controller-Set-Part-Layer') {
|
||||
data = JSON.parse(message);
|
||||
for (name in data) {
|
||||
var layer = data[name];
|
||||
if (controllerLeft) {
|
||||
controllerLeft.setLayerForPart(name, layer);
|
||||
}
|
||||
if (controllerRight) {
|
||||
controllerRight.setLayerForPart(name, layer);
|
||||
}
|
||||
}
|
||||
} else if (channel === 'Hifi-Object-Manipulation') {
|
||||
if (HIDE_CONTROLLERS_ON_EQUIP) {
|
||||
data = JSON.parse(message);
|
||||
visible = data.action !== 'equip';
|
||||
if (data.joint === "LeftHand") {
|
||||
self.setLeftVisible(visible);
|
||||
} else if (data.joint === "RightHand") {
|
||||
self.setRightVisible(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Messages.messageReceived.connect(handleMessages);
|
||||
|
||||
this.deleteControllerDisplays = function() {
|
||||
if (controllerLeft) {
|
||||
deleteControllerDisplay(controllerLeft);
|
||||
controllerLeft = null;
|
||||
}
|
||||
if (controllerRight) {
|
||||
deleteControllerDisplay(controllerRight);
|
||||
controllerRight = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.destroy = function() {
|
||||
Messages.messageReceived.disconnect(handleMessages);
|
||||
|
||||
HMD.displayModeChanged.disconnect(updateControllers);
|
||||
HMD.shouldShowHandControllersChanged.disconnect(updateControllers);
|
||||
|
||||
self.deleteControllerDisplays();
|
||||
};
|
||||
|
||||
HMD.displayModeChanged.connect(updateControllers);
|
||||
HMD.shouldShowHandControllersChanged.connect(updateControllers);
|
||||
MyAvatar.sensorToWorldScaleChanged.connect(resizeControllers);
|
||||
|
||||
updateControllers();
|
||||
};
|
||||
|
||||
var controllerDisplayManager = new ControllerDisplayManager();
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
controllerDisplayManager.destroy();
|
||||
});
|
||||
|
||||
}());
|
|
@ -0,0 +1,83 @@
|
|||
"use strict";
|
||||
|
||||
// disableOtherModule.js
|
||||
//
|
||||
// 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, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
|
||||
makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName, Messages
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
(function() {
|
||||
function DisableModules(hand) {
|
||||
this.hand = hand;
|
||||
this.disableModules = false;
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
90,
|
||||
this.hand === RIGHT_HAND ?
|
||||
["rightHand", "rightHandEquip", "rightHandTrigger"] :
|
||||
["leftHand", "leftHandEquip", "leftHandTrigger"],
|
||||
[],
|
||||
100);
|
||||
|
||||
this.isReady = function(controllerData) {
|
||||
if (this.disableModules) {
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.run = function(controllerData) {
|
||||
var teleportModuleName = this.hand === RIGHT_HAND ? "RightTeleporter" : "LeftTeleporter";
|
||||
var teleportModule = getEnabledModuleByName(teleportModuleName);
|
||||
|
||||
if (teleportModule) {
|
||||
var ready = teleportModule.isReady(controllerData);
|
||||
if (ready.active) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
if (!this.disableModules) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
return makeRunningValues(true, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftDisableModules = new DisableModules(LEFT_HAND);
|
||||
var rightDisableModules = new DisableModules(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftDisableModules", leftDisableModules);
|
||||
enableDispatcherModule("RightDisableModules", rightDisableModules);
|
||||
function handleMessage(channel, message, sender) {
|
||||
if (sender === MyAvatar.sessionUUID) {
|
||||
if (channel === 'Hifi-Hand-Disabler') {
|
||||
if (message === 'left') {
|
||||
leftDisableModules.disableModules = true;
|
||||
} else if (message === 'right') {
|
||||
rightDisableModules.disableModules = true;
|
||||
} else if (message === 'both') {
|
||||
leftDisableModules.disableModules = true;
|
||||
rightDisableModules.disableModules = true;
|
||||
} else if (message === 'none') {
|
||||
leftDisableModules.disableModules = false;
|
||||
rightDisableModules.disableModules = false;
|
||||
} else {
|
||||
print("disableOtherModule -- unknown command: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.subscribe('Hifi-Hand-Disabler');
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftDisableModules");
|
||||
disableDispatcherModule("RightDisableModules");
|
||||
}
|
||||
Messages.messageReceived.connect(handleMessage);
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,867 @@
|
|||
"use strict";
|
||||
|
||||
// equipEntity.js
|
||||
//
|
||||
// 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, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, Camera, print, getControllerJointIndex,
|
||||
enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters,
|
||||
makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic,
|
||||
entityIsCloneable, cloneEntity, DISPATCHER_PROPERTIES, Uuid, isInEditMode, getGrabbableData,
|
||||
entityIsEquippable, HMD
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/Xform.js");
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
Script.include("/~/system/libraries/cloneEntityUtils.js");
|
||||
|
||||
|
||||
var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx";
|
||||
var EQUIP_SPHERE_SCALE_FACTOR = 0.65;
|
||||
|
||||
|
||||
// Each overlayInfoSet describes a single equip hotspot.
|
||||
// It is an object with the following keys:
|
||||
// timestamp - last time this object was updated, used to delete stale hotspot overlays.
|
||||
// entityID - entity assosicated with this hotspot
|
||||
// localPosition - position relative to the entity
|
||||
// hotspot - hotspot object
|
||||
// overlays - array of overlay objects created by Overlay.addOverlay()
|
||||
// currentSize - current animated scale value
|
||||
// targetSize - the target of our scale animations
|
||||
// type - "sphere" or "model".
|
||||
function EquipHotspotBuddy() {
|
||||
// holds map from {string} hotspot.key to {object} overlayInfoSet.
|
||||
this.map = {};
|
||||
|
||||
// array of all hotspots that are highlighed.
|
||||
this.highlightedHotspots = [];
|
||||
}
|
||||
EquipHotspotBuddy.prototype.clear = function() {
|
||||
var keys = Object.keys(this.map);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var overlayInfoSet = this.map[keys[i]];
|
||||
this.deleteOverlayInfoSet(overlayInfoSet);
|
||||
}
|
||||
this.map = {};
|
||||
this.highlightedHotspots = [];
|
||||
};
|
||||
EquipHotspotBuddy.prototype.highlightHotspot = function(hotspot) {
|
||||
this.highlightedHotspots.push(hotspot.key);
|
||||
};
|
||||
EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) {
|
||||
var overlayInfoSet = this.map[hotspot.key];
|
||||
if (!overlayInfoSet) {
|
||||
// create a new overlayInfoSet
|
||||
overlayInfoSet = {
|
||||
timestamp: timestamp,
|
||||
entityID: hotspot.entityID,
|
||||
localPosition: hotspot.localPosition,
|
||||
hotspot: hotspot,
|
||||
currentSize: 0,
|
||||
targetSize: 1,
|
||||
overlays: []
|
||||
};
|
||||
|
||||
var dimensions = hotspot.radius * 2 * EQUIP_SPHERE_SCALE_FACTOR;
|
||||
|
||||
if (hotspot.indicatorURL) {
|
||||
dimensions = hotspot.indicatorScale;
|
||||
}
|
||||
|
||||
// override default sphere with a user specified model, if it exists.
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
|
||||
name: "hotspot overlay",
|
||||
url: hotspot.indicatorURL ? hotspot.indicatorURL : DEFAULT_SPHERE_MODEL_URL,
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: 1
|
||||
},
|
||||
dimensions: dimensions,
|
||||
ignoreRayIntersection: true
|
||||
}));
|
||||
overlayInfoSet.type = "model";
|
||||
this.map[hotspot.key] = overlayInfoSet;
|
||||
} else {
|
||||
overlayInfoSet.timestamp = timestamp;
|
||||
}
|
||||
};
|
||||
EquipHotspotBuddy.prototype.updateHotspots = function(hotspots, timestamp) {
|
||||
var _this = this;
|
||||
hotspots.forEach(function(hotspot) {
|
||||
_this.updateHotspot(hotspot, timestamp);
|
||||
});
|
||||
this.highlightedHotspots = [];
|
||||
};
|
||||
EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerData) {
|
||||
|
||||
var HIGHLIGHT_SIZE = 1.1;
|
||||
var NORMAL_SIZE = 1.0;
|
||||
|
||||
var keys = Object.keys(this.map);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var overlayInfoSet = this.map[keys[i]];
|
||||
|
||||
// this overlayInfo is highlighted.
|
||||
if (this.highlightedHotspots.indexOf(keys[i]) !== -1) {
|
||||
overlayInfoSet.targetSize = HIGHLIGHT_SIZE;
|
||||
} else {
|
||||
overlayInfoSet.targetSize = NORMAL_SIZE;
|
||||
}
|
||||
|
||||
// start to fade out this hotspot.
|
||||
if (overlayInfoSet.timestamp !== timestamp) {
|
||||
overlayInfoSet.targetSize = 0;
|
||||
}
|
||||
|
||||
// animate the size.
|
||||
var SIZE_TIMESCALE = 0.1;
|
||||
var tau = deltaTime / SIZE_TIMESCALE;
|
||||
if (tau > 1.0) {
|
||||
tau = 1.0;
|
||||
}
|
||||
overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau;
|
||||
|
||||
if (overlayInfoSet.timestamp !== timestamp && overlayInfoSet.currentSize <= 0.05) {
|
||||
// this is an old overlay, that has finished fading out, delete it!
|
||||
overlayInfoSet.overlays.forEach(Overlays.deleteOverlay);
|
||||
delete this.map[keys[i]];
|
||||
} else {
|
||||
// update overlay position, rotation to follow the object it's attached to.
|
||||
var props = controllerData.nearbyEntityPropertiesByID[overlayInfoSet.entityID];
|
||||
if (props) {
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
var position = entityXform.xformPoint(overlayInfoSet.localPosition);
|
||||
|
||||
var dimensions;
|
||||
if (overlayInfoSet.hotspot.indicatorURL) {
|
||||
var ratio = overlayInfoSet.currentSize / overlayInfoSet.targetSize;
|
||||
dimensions = {
|
||||
x: overlayInfoSet.hotspot.dimensions.x * ratio,
|
||||
y: overlayInfoSet.hotspot.dimensions.y * ratio,
|
||||
z: overlayInfoSet.hotspot.dimensions.z * ratio
|
||||
};
|
||||
} else {
|
||||
dimensions = (overlayInfoSet.hotspot.radius / 2) * overlayInfoSet.currentSize;
|
||||
}
|
||||
|
||||
overlayInfoSet.overlays.forEach(function(overlay) {
|
||||
Overlays.editOverlay(overlay, {
|
||||
position: position,
|
||||
rotation: props.rotation,
|
||||
dimensions: dimensions
|
||||
});
|
||||
});
|
||||
} else {
|
||||
overlayInfoSet.overlays.forEach(Overlays.deleteOverlay);
|
||||
delete this.map[keys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints";
|
||||
|
||||
var HAPTIC_PULSE_STRENGTH = 1.0;
|
||||
var HAPTIC_PULSE_DURATION = 13.0;
|
||||
var HAPTIC_TEXTURE_STRENGTH = 0.1;
|
||||
var HAPTIC_TEXTURE_DURATION = 3.0;
|
||||
var HAPTIC_TEXTURE_DISTANCE = 0.002;
|
||||
var HAPTIC_DEQUIP_STRENGTH = 0.75;
|
||||
var HAPTIC_DEQUIP_DURATION = 50.0;
|
||||
|
||||
var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing
|
||||
var TRIGGER_OFF_VALUE = 0.1;
|
||||
var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab
|
||||
var BUMPER_ON_VALUE = 0.5;
|
||||
var ATTACHPOINT_MAX_DISTANCE = 3.0;
|
||||
|
||||
// var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}";
|
||||
|
||||
var UNEQUIP_KEY = "u";
|
||||
|
||||
function getWearableData(props) {
|
||||
if (props.grab.equippable) {
|
||||
return {
|
||||
joints: {
|
||||
LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ],
|
||||
RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ]
|
||||
},
|
||||
indicatorURL: props.grab.equippableIndicatorURL,
|
||||
indicatorScale: props.grab.equippableIndicatorScale,
|
||||
indicatorOffset: props.grab.equippableIndicatorOffset
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getAttachPointSettings() {
|
||||
try {
|
||||
var str = Settings.getValue(ATTACH_POINT_SETTINGS);
|
||||
if (str === "false" || str === "") {
|
||||
return {};
|
||||
} else {
|
||||
return JSON.parse(str);
|
||||
}
|
||||
} catch (err) {
|
||||
print("Error parsing attachPointSettings: " + err);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function setAttachPointSettings(attachPointSettings) {
|
||||
var str = JSON.stringify(attachPointSettings);
|
||||
Settings.setValue(ATTACH_POINT_SETTINGS, str);
|
||||
}
|
||||
|
||||
function getAttachPointForHotspotFromSettings(hotspot, hand) {
|
||||
var skeletonModelURL = MyAvatar.skeletonModelURL;
|
||||
var attachPointSettings = getAttachPointSettings();
|
||||
var avatarSettingsData = attachPointSettings[skeletonModelURL];
|
||||
if (avatarSettingsData) {
|
||||
var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand";
|
||||
var joints = avatarSettingsData[hotspot.key];
|
||||
if (joints) {
|
||||
// make sure they are reasonable
|
||||
if (joints[jointName] && joints[jointName][0] &&
|
||||
Vec3.length(joints[jointName][0]) > ATTACHPOINT_MAX_DISTANCE) {
|
||||
print("equipEntity -- Warning: rejecting settings attachPoint " + Vec3.length(joints[jointName][0]));
|
||||
return undefined;
|
||||
}
|
||||
return joints[jointName];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) {
|
||||
var attachPointSettings = getAttachPointSettings();
|
||||
var skeletonModelURL = MyAvatar.skeletonModelURL;
|
||||
var avatarSettingsData = attachPointSettings[skeletonModelURL];
|
||||
if (!avatarSettingsData) {
|
||||
avatarSettingsData = {};
|
||||
attachPointSettings[skeletonModelURL] = avatarSettingsData;
|
||||
}
|
||||
var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand";
|
||||
var joints = avatarSettingsData[hotspot.key];
|
||||
if (!joints) {
|
||||
joints = {};
|
||||
avatarSettingsData[hotspot.key] = joints;
|
||||
}
|
||||
joints[jointName] = [offsetPosition, offsetRotation];
|
||||
setAttachPointSettings(attachPointSettings);
|
||||
}
|
||||
|
||||
function clearAttachPoints() {
|
||||
setAttachPointSettings({});
|
||||
}
|
||||
|
||||
function EquipEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.targetEntityID = null;
|
||||
this.prevHandIsUpsideDown = false;
|
||||
this.triggerValue = 0;
|
||||
this.messageGrabEntity = false;
|
||||
this.grabEntityProps = null;
|
||||
this.shouldSendStart = false;
|
||||
this.equipedWithSecondary = false;
|
||||
this.handHasBeenRightsideUp = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
115,
|
||||
this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip"] : ["leftHand", "leftHandEquip"],
|
||||
[],
|
||||
100);
|
||||
|
||||
var equipHotspotBuddy = new EquipHotspotBuddy();
|
||||
|
||||
this.setMessageGrabData = function(entityProperties) {
|
||||
if (entityProperties) {
|
||||
this.messageGrabEntity = true;
|
||||
this.grabEntityProps = entityProperties;
|
||||
}
|
||||
};
|
||||
|
||||
// returns a list of all equip-hotspots assosiated with this entity.
|
||||
// @param {UUID} entityID
|
||||
// @returns {Object[]} array of objects with the following fields.
|
||||
// * key {string} a string that can be used to uniquely identify this hotspot
|
||||
// * entityID {UUID}
|
||||
// * localPosition {Vec3} position of the hotspot in object space.
|
||||
// * worldPosition {vec3} position of the hotspot in world space.
|
||||
// * radius {number} radius of equip hotspot
|
||||
// * joints {Object} keys are joint names values are arrays of two elements:
|
||||
// offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint.
|
||||
// * indicatorURL {string} url for model to use instead of default sphere.
|
||||
// * indicatorScale {Vec3} scale factor for model
|
||||
this.collectEquipHotspots = function(props) {
|
||||
var result = [];
|
||||
var entityID = props.id;
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
|
||||
var wearableProps = getWearableData(props);
|
||||
var sensorToScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
if (wearableProps && wearableProps.joints) {
|
||||
result.push({
|
||||
key: entityID.toString() + "0",
|
||||
entityID: entityID,
|
||||
localPosition: wearableProps.indicatorOffset,
|
||||
worldPosition: entityXform.pos,
|
||||
radius: ((wearableProps.indicatorScale.x +
|
||||
wearableProps.indicatorScale.y +
|
||||
wearableProps.indicatorScale.z) / 3) * sensorToScaleFactor,
|
||||
dimensions: wearableProps.indicatorScale,
|
||||
joints: wearableProps.joints,
|
||||
indicatorURL: wearableProps.indicatorURL,
|
||||
indicatorScale: wearableProps.indicatorScale,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
this.hotspotIsEquippable = function(hotspot, controllerData) {
|
||||
var props = controllerData.nearbyEntityPropertiesByID[hotspot.entityID];
|
||||
|
||||
var hasParent = true;
|
||||
if (props.parentID === Uuid.NULL) {
|
||||
hasParent = false;
|
||||
}
|
||||
|
||||
if (hasParent || entityHasActions(hotspot.entityID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
this.handToController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
};
|
||||
|
||||
this.updateSmoothedTrigger = function(controllerData) {
|
||||
var triggerValue = controllerData.triggerValues[this.hand];
|
||||
// smooth out trigger value
|
||||
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
|
||||
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
|
||||
};
|
||||
|
||||
this.triggerSmoothedGrab = function() {
|
||||
return this.triggerClicked;
|
||||
};
|
||||
|
||||
this.triggerSmoothedSqueezed = function() {
|
||||
return this.triggerValue > TRIGGER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.triggerSmoothedReleased = function() {
|
||||
return this.triggerValue < TRIGGER_OFF_VALUE;
|
||||
};
|
||||
|
||||
this.secondaryReleased = function() {
|
||||
return this.rawSecondaryValue < BUMPER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.secondarySmoothedSqueezed = function() {
|
||||
return this.rawSecondaryValue > BUMPER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) {
|
||||
var _this = this;
|
||||
var collectedHotspots = flatten(candidateEntityProps.map(function(props) {
|
||||
return _this.collectEquipHotspots(props);
|
||||
}));
|
||||
var controllerLocation = controllerData.controllerLocations[_this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var equippableHotspots = collectedHotspots.filter(function(hotspot) {
|
||||
var hotspotDistance = Vec3.distance(hotspot.worldPosition, worldControllerPosition);
|
||||
return _this.hotspotIsEquippable(hotspot, controllerData) &&
|
||||
hotspotDistance < hotspot.radius;
|
||||
});
|
||||
return equippableHotspots;
|
||||
};
|
||||
|
||||
this.cloneHotspot = function(props, controllerData) {
|
||||
if (entityIsCloneable(props)) {
|
||||
var cloneID = cloneEntity(props);
|
||||
return cloneID;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
this.chooseBestEquipHotspot = function(candidateEntityProps, controllerData) {
|
||||
var equippableHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData);
|
||||
if (equippableHotspots.length > 0) {
|
||||
// sort by distance;
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
equippableHotspots.sort(function(a, b) {
|
||||
var aDistance = Vec3.distance(a.worldPosition, worldControllerPosition);
|
||||
var bDistance = Vec3.distance(b.worldPosition, worldControllerPosition);
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
return equippableHotspots[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
this.dropGestureReset = function() {
|
||||
this.prevHandIsUpsideDown = false;
|
||||
};
|
||||
|
||||
this.dropGestureProcess = function (deltaTime) {
|
||||
var worldHandRotation = getControllerWorldLocation(this.handToController(), true).orientation;
|
||||
var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, z: 0 } : { x: -1, y: 0, z: 0 };
|
||||
var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis);
|
||||
var DOWN = { x: 0, y: -1, z: 0 };
|
||||
|
||||
var DROP_ANGLE = Math.PI / 3;
|
||||
var HYSTERESIS_FACTOR = 1.1;
|
||||
var ROTATION_ENTER_THRESHOLD = Math.cos(DROP_ANGLE);
|
||||
var ROTATION_EXIT_THRESHOLD = Math.cos(DROP_ANGLE * HYSTERESIS_FACTOR);
|
||||
var rotationThreshold = this.prevHandIsUpsideDown ? ROTATION_EXIT_THRESHOLD : ROTATION_ENTER_THRESHOLD;
|
||||
|
||||
var handIsUpsideDown = false;
|
||||
if (Vec3.dot(worldHandUpAxis, DOWN) > rotationThreshold) {
|
||||
handIsUpsideDown = true;
|
||||
}
|
||||
|
||||
if (handIsUpsideDown !== this.prevHandIsUpsideDown) {
|
||||
this.prevHandIsUpsideDown = handIsUpsideDown;
|
||||
Controller.triggerHapticPulse(HAPTIC_DEQUIP_STRENGTH, HAPTIC_DEQUIP_DURATION, this.hand);
|
||||
}
|
||||
|
||||
return handIsUpsideDown;
|
||||
};
|
||||
|
||||
this.clearEquipHaptics = function() {
|
||||
this.prevPotentialEquipHotspot = null;
|
||||
};
|
||||
|
||||
this.updateEquipHaptics = function(potentialEquipHotspot, currentLocation) {
|
||||
if (potentialEquipHotspot && !this.prevPotentialEquipHotspot ||
|
||||
!potentialEquipHotspot && this.prevPotentialEquipHotspot) {
|
||||
Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand);
|
||||
this.lastHapticPulseLocation = currentLocation;
|
||||
} else if (potentialEquipHotspot &&
|
||||
Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) {
|
||||
Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand);
|
||||
this.lastHapticPulseLocation = currentLocation;
|
||||
}
|
||||
this.prevPotentialEquipHotspot = potentialEquipHotspot;
|
||||
};
|
||||
|
||||
this.startEquipEntity = function (controllerData) {
|
||||
var _this = this;
|
||||
|
||||
this.dropGestureReset();
|
||||
this.clearEquipHaptics();
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
|
||||
var grabbedProperties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||
var grabData = getGrabbableData(grabbedProperties);
|
||||
|
||||
// if an object is "equipped" and has a predefined offset, use it.
|
||||
if (this.grabbedHotspot) {
|
||||
var offsets = getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand);
|
||||
if (offsets) {
|
||||
this.offsetPosition = offsets[0];
|
||||
this.offsetRotation = offsets[1];
|
||||
} else {
|
||||
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
||||
if (this.grabbedHotspot.joints[handJointName]) {
|
||||
this.offsetPosition = this.grabbedHotspot.joints[handJointName][0];
|
||||
this.offsetRotation = this.grabbedHotspot.joints[handJointName][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var handJointIndex;
|
||||
if (HMD.mounted && HMD.isHandControllerAvailable() && grabData.grabFollowsController) {
|
||||
handJointIndex = this.controllerJointIndex;
|
||||
} else {
|
||||
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
}
|
||||
|
||||
var reparentProps = {
|
||||
parentID: MyAvatar.SELF_ID,
|
||||
parentJointIndex: handJointIndex,
|
||||
localVelocity: {x: 0, y: 0, z: 0},
|
||||
localAngularVelocity: {x: 0, y: 0, z: 0},
|
||||
localPosition: this.offsetPosition,
|
||||
localRotation: this.offsetRotation
|
||||
};
|
||||
|
||||
var isClone = false;
|
||||
if (entityIsCloneable(grabbedProperties)) {
|
||||
var cloneID = this.cloneHotspot(grabbedProperties, controllerData);
|
||||
this.targetEntityID = cloneID;
|
||||
controllerData.nearbyEntityPropertiesByID[this.targetEntityID] = grabbedProperties;
|
||||
isClone = true;
|
||||
} else if (grabbedProperties.locked) {
|
||||
this.grabbedHotspot = null;
|
||||
this.targetEntityID = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// HACK -- when
|
||||
// https://highfidelity.fogbugz.com/f/cases/21767/entity-edits-shortly-after-an-add-often-fail
|
||||
// is resolved, this can just be an editEntity rather than a setTimeout.
|
||||
this.editDelayTimeout = Script.setTimeout(function () {
|
||||
_this.editDelayTimeout = null;
|
||||
Entities.editEntity(_this.targetEntityID, reparentProps);
|
||||
}, 100);
|
||||
|
||||
// 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',
|
||||
grabbedEntity: this.targetEntityID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
|
||||
var grabEquipCheck = function() {
|
||||
var args = [_this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(_this.targetEntityID, "startEquip", args);
|
||||
};
|
||||
|
||||
if (isClone) {
|
||||
// 100 ms seems to be sufficient time to force the check even occur after the object has been initialized.
|
||||
Script.setTimeout(grabEquipCheck, 100);
|
||||
}
|
||||
};
|
||||
|
||||
this.endEquipEntity = function () {
|
||||
|
||||
if (this.editDelayTimeout) {
|
||||
Script.clearTimeout(this.editDelayTimeout);
|
||||
this.editDelayTimeout = null;
|
||||
}
|
||||
|
||||
this.storeAttachPointInSettings();
|
||||
Entities.editEntity(this.targetEntityID, {
|
||||
parentID: Uuid.NULL,
|
||||
parentJointIndex: -1
|
||||
});
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args);
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'release',
|
||||
grabbedEntity: this.targetEntityID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
|
||||
ensureDynamic(this.targetEntityID);
|
||||
this.targetEntityID = null;
|
||||
this.messageGrabEntity = false;
|
||||
this.grabEntityProps = null;
|
||||
};
|
||||
|
||||
this.updateInputs = function (controllerData) {
|
||||
this.rawTriggerValue = controllerData.triggerValues[this.hand];
|
||||
this.triggerClicked = controllerData.triggerClicks[this.hand];
|
||||
this.rawSecondaryValue = controllerData.secondaryValues[this.hand];
|
||||
this.updateSmoothedTrigger(controllerData);
|
||||
};
|
||||
|
||||
this.checkNearbyHotspots = function (controllerData, deltaTime, timestamp) {
|
||||
this.controllerJointIndex = getControllerJointIndex(this.hand);
|
||||
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.waitForTriggerRelease = false;
|
||||
}
|
||||
|
||||
var controllerLocation = getControllerWorldLocation(this.handToController(), true);
|
||||
var worldHandPosition = controllerLocation.position;
|
||||
var candidateEntityProps = controllerData.nearbyEntityProperties[this.hand];
|
||||
|
||||
|
||||
var potentialEquipHotspot = null;
|
||||
if (this.messageGrabEntity) {
|
||||
var hotspots = this.collectEquipHotspots(this.grabEntityProps);
|
||||
if (hotspots.length > -1) {
|
||||
potentialEquipHotspot = hotspots[0];
|
||||
}
|
||||
} else {
|
||||
potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData);
|
||||
}
|
||||
|
||||
if (!this.waitForTriggerRelease) {
|
||||
this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition);
|
||||
}
|
||||
|
||||
var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData);
|
||||
equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp);
|
||||
if (potentialEquipHotspot) {
|
||||
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
||||
}
|
||||
|
||||
equipHotspotBuddy.update(deltaTime, timestamp, controllerData);
|
||||
|
||||
// if the potentialHotspot is cloneable, clone it and return it
|
||||
// if the potentialHotspot is not cloneable and locked return null
|
||||
if (potentialEquipHotspot &&
|
||||
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
|
||||
this.messageGrabEntity)) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.targetEntityID = this.grabbedHotspot.entityID;
|
||||
this.startEquipEntity(controllerData);
|
||||
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
} else {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.isTargetIDValid = function(controllerData) {
|
||||
var entityProperties = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||
return entityProperties && "type" in entityProperties;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData, deltaTime) {
|
||||
var timestamp = Date.now();
|
||||
this.updateInputs(controllerData);
|
||||
this.handHasBeenRightsideUp = false;
|
||||
return this.checkNearbyHotspots(controllerData, deltaTime, timestamp);
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
var timestamp = Date.now();
|
||||
this.updateInputs(controllerData);
|
||||
|
||||
if (!this.messageGrabEntity && !this.isTargetIDValid(controllerData)) {
|
||||
this.endEquipEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
if (!this.targetEntityID) {
|
||||
return this.checkNearbyHotspots(controllerData, deltaTime, timestamp);
|
||||
}
|
||||
|
||||
if (controllerData.secondaryValues[this.hand] && !this.equipedWithSecondary) {
|
||||
// this.secondaryReleased() will always be true when not depressed
|
||||
// so we cannot simply rely on that for release - ensure that the
|
||||
// trigger was first "prepared" by being pushed in before the release
|
||||
this.preparingHoldRelease = true;
|
||||
}
|
||||
|
||||
if (this.preparingHoldRelease && !controllerData.secondaryValues[this.hand]) {
|
||||
// we have an equipped object and the secondary trigger was released
|
||||
// short-circuit the other checks and release it
|
||||
this.preparingHoldRelease = false;
|
||||
this.endEquipEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var handIsUpsideDown = this.dropGestureProcess(deltaTime);
|
||||
var dropDetected = false;
|
||||
if (this.handHasBeenRightsideUp) {
|
||||
dropDetected = handIsUpsideDown;
|
||||
}
|
||||
if (!handIsUpsideDown) {
|
||||
this.handHasBeenRightsideUp = true;
|
||||
}
|
||||
|
||||
if (this.triggerSmoothedReleased() || this.secondaryReleased()) {
|
||||
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;
|
||||
if (this.secondaryReleased() && this.equipedWithSecondary) {
|
||||
this.equipedWithSecondary = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dropDetected && this.prevDropDetected !== dropDetected) {
|
||||
this.waitForTriggerRelease = true;
|
||||
}
|
||||
|
||||
// highlight the grabbed hotspot when the dropGesture is detected.
|
||||
if (dropDetected && this.grabbedHotspot) {
|
||||
equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp);
|
||||
equipHotspotBuddy.highlightHotspot(this.grabbedHotspot);
|
||||
}
|
||||
|
||||
if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) {
|
||||
this.waitForTriggerRelease = true;
|
||||
// store the offset attach points into preferences.
|
||||
this.endEquipEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
this.prevDropDetected = dropDetected;
|
||||
|
||||
equipHotspotBuddy.update(deltaTime, timestamp, controllerData);
|
||||
|
||||
if (!this.shouldSendStart) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueEquip", args);
|
||||
}
|
||||
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
};
|
||||
|
||||
this.storeAttachPointInSettings = function() {
|
||||
if (this.grabbedHotspot && this.targetEntityID) {
|
||||
var prefProps = Entities.getEntityProperties(this.targetEntityID, ["localPosition", "localRotation"]);
|
||||
if (prefProps && prefProps.localPosition && prefProps.localRotation) {
|
||||
storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand,
|
||||
prefProps.localPosition, prefProps.localRotation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
if (this.targetEntityID) {
|
||||
this.endEquipEntity();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var handleMessage = function(channel, message, sender) {
|
||||
var data;
|
||||
if (sender === MyAvatar.sessionUUID) {
|
||||
if (channel === 'Hifi-Hand-Grab') {
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity;
|
||||
var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES);
|
||||
entityProperties.id = data.entityID;
|
||||
equipModule.setMessageGrabData(entityProperties);
|
||||
} catch (e) {
|
||||
print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message);
|
||||
}
|
||||
} else if (channel === 'Hifi-Hand-Drop') {
|
||||
if (message === "left") {
|
||||
leftEquipEntity.endEquipEntity();
|
||||
} else if (message === "right") {
|
||||
rightEquipEntity.endEquipEntity();
|
||||
} else if (message === "both") {
|
||||
leftEquipEntity.endEquipEntity();
|
||||
rightEquipEntity.endEquipEntity();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var clearGrabActions = function(entityID) {
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
var myGrabTag = "grab-" + MyAvatar.sessionUUID;
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
var actionID = actionIDs[actionIndex];
|
||||
var actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
var tag = actionArguments.tag;
|
||||
if (tag === myGrabTag) {
|
||||
Entities.deleteAction(entityID, actionID);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var onMousePress = function(event) {
|
||||
if (isInEditMode() || !event.isLeftButton) { // don't consider any left clicks on the entity while in edit
|
||||
return;
|
||||
}
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects) {
|
||||
var entityID = intersection.entityID;
|
||||
var entityProperties = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
||||
entityProperties.id = entityID;
|
||||
var hasEquipData = getWearableData(entityProperties);
|
||||
if (hasEquipData && entityIsEquippable(entityProperties)) {
|
||||
entityProperties.id = entityID;
|
||||
var rightHandPosition = MyAvatar.getJointPosition("RightHand");
|
||||
var leftHandPosition = MyAvatar.getJointPosition("LeftHand");
|
||||
var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition);
|
||||
var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition);
|
||||
var leftHandAvailable = leftEquipEntity.targetEntityID === null;
|
||||
var rightHandAvailable = rightEquipEntity.targetEntityID === null;
|
||||
if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) {
|
||||
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
|
||||
clearGrabActions(entityID);
|
||||
rightEquipEntity.setMessageGrabData(entityProperties);
|
||||
} else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) {
|
||||
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
|
||||
clearGrabActions(entityID);
|
||||
leftEquipEntity.setMessageGrabData(entityProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var onKeyPress = function(event) {
|
||||
if (event.text.toLowerCase() === UNEQUIP_KEY) {
|
||||
if (rightEquipEntity.targetEntityID) {
|
||||
rightEquipEntity.endEquipEntity();
|
||||
}
|
||||
if (leftEquipEntity.targetEntityID) {
|
||||
leftEquipEntity.endEquipEntity();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var deleteEntity = function(entityID) {
|
||||
if (rightEquipEntity.targetEntityID === entityID) {
|
||||
rightEquipEntity.endEquipEntity();
|
||||
}
|
||||
if (leftEquipEntity.targetEntityID === entityID) {
|
||||
leftEquipEntity.endEquipEntity();
|
||||
}
|
||||
};
|
||||
|
||||
var clearEntities = function() {
|
||||
if (rightEquipEntity.targetEntityID) {
|
||||
rightEquipEntity.endEquipEntity();
|
||||
}
|
||||
if (leftEquipEntity.targetEntityID) {
|
||||
leftEquipEntity.endEquipEntity();
|
||||
}
|
||||
};
|
||||
|
||||
Messages.subscribe('Hifi-Hand-Grab');
|
||||
Messages.subscribe('Hifi-Hand-Drop');
|
||||
Messages.messageReceived.connect(handleMessage);
|
||||
Controller.mousePressEvent.connect(onMousePress);
|
||||
Controller.keyPressEvent.connect(onKeyPress);
|
||||
Entities.deletingEntity.connect(deleteEntity);
|
||||
Entities.clearingEntities.connect(clearEntities);
|
||||
|
||||
var leftEquipEntity = new EquipEntity(LEFT_HAND);
|
||||
var rightEquipEntity = new EquipEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftEquipEntity", leftEquipEntity);
|
||||
enableDispatcherModule("RightEquipEntity", rightEquipEntity);
|
||||
|
||||
function cleanup() {
|
||||
leftEquipEntity.cleanup();
|
||||
rightEquipEntity.cleanup();
|
||||
disableDispatcherModule("LeftEquipEntity");
|
||||
disableDispatcherModule("RightEquipEntity");
|
||||
clearAttachPoints();
|
||||
Messages.messageReceived.disconnect(handleMessage);
|
||||
Controller.mousePressEvent.disconnect(onMousePress);
|
||||
Controller.keyPressEvent.disconnect(onKeyPress);
|
||||
Entities.deletingEntity.disconnect(deleteEntity);
|
||||
Entities.clearingEntities.disconnect(clearEntities);
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,591 @@
|
|||
"use strict";
|
||||
|
||||
// farActionGrabEntity.js
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* jslint bitwise: true */
|
||||
|
||||
/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat,
|
||||
getEnabledModuleByName, makeRunningValues, Entities,
|
||||
enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable, entityIsGrabbable,
|
||||
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
|
||||
TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
|
||||
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD,
|
||||
Picks, makeLaserLockInfo, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST,
|
||||
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES, Uuid, Picks
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() {
|
||||
|
||||
var MARGIN = 25;
|
||||
|
||||
function TargetObject(entityID, entityProps) {
|
||||
this.entityID = entityID;
|
||||
this.entityProps = entityProps;
|
||||
this.targetEntityID = null;
|
||||
this.targetEntityProps = null;
|
||||
this.previousCollisionStatus = null;
|
||||
this.madeDynamic = null;
|
||||
|
||||
this.makeDynamic = function() {
|
||||
if (this.targetEntityID) {
|
||||
var newProps = {
|
||||
dynamic: true,
|
||||
collisionless: true
|
||||
};
|
||||
this.previousCollisionStatus = this.targetEntityProps.collisionless;
|
||||
Entities.editEntity(this.targetEntityID, newProps);
|
||||
this.madeDynamic = true;
|
||||
}
|
||||
};
|
||||
|
||||
this.restoreTargetEntityOriginalProps = function() {
|
||||
if (this.madeDynamic) {
|
||||
var props = {};
|
||||
props.dynamic = false;
|
||||
props.collisionless = this.previousCollisionStatus;
|
||||
var zeroVector = {x: 0, y: 0, z:0};
|
||||
props.localVelocity = zeroVector;
|
||||
props.localRotation = zeroVector;
|
||||
Entities.editEntity(this.targetEntityID, props);
|
||||
}
|
||||
};
|
||||
|
||||
this.getTargetEntity = function() {
|
||||
var parentPropsLength = this.parentProps.length;
|
||||
if (parentPropsLength !== 0) {
|
||||
var targetEntity = {
|
||||
id: this.parentProps[parentPropsLength - 1].id,
|
||||
props: this.parentProps[parentPropsLength - 1]};
|
||||
this.targetEntityID = targetEntity.id;
|
||||
this.targetEntityProps = targetEntity.props;
|
||||
return targetEntity;
|
||||
}
|
||||
this.targetEntityID = this.entityID;
|
||||
this.targetEntityProps = this.entityProps;
|
||||
return {
|
||||
id: this.entityID,
|
||||
props: this.entityProps};
|
||||
};
|
||||
}
|
||||
|
||||
function FarActionGrabEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.grabbedThingID = null;
|
||||
this.targetObject = null;
|
||||
this.actionID = null; // action this script created...
|
||||
this.entityToLockOnto = null;
|
||||
this.potentialEntityWithContextOverlay = false;
|
||||
this.entityWithContextOverlay = false;
|
||||
this.contextOverlayTimer = false;
|
||||
this.locked = false;
|
||||
this.reticleMinX = MARGIN;
|
||||
this.reticleMaxX = null;
|
||||
this.reticleMinY = MARGIN;
|
||||
this.reticleMaxY = null;
|
||||
|
||||
this.ignoredEntities = [];
|
||||
|
||||
var ACTION_TTL = 15; // seconds
|
||||
|
||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object
|
||||
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||
var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified
|
||||
var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
550,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(this.hand, false));
|
||||
|
||||
|
||||
this.handToController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
};
|
||||
|
||||
this.distanceGrabTimescale = function(mass, distance) {
|
||||
var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass /
|
||||
DISTANCE_HOLDING_UNITY_MASS * distance /
|
||||
DISTANCE_HOLDING_UNITY_DISTANCE;
|
||||
if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) {
|
||||
timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME;
|
||||
}
|
||||
return timeScale;
|
||||
};
|
||||
|
||||
this.getMass = function(dimensions, density) {
|
||||
return (dimensions.x * dimensions.y * dimensions.z) * density;
|
||||
};
|
||||
|
||||
this.startFarGrabAction = function (controllerData, grabbedProperties) {
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var worldControllerRotation = controllerLocation.orientation;
|
||||
|
||||
// transform the position into room space
|
||||
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
// add the action and initialize some variables
|
||||
this.currentObjectPosition = grabbedProperties.position;
|
||||
this.currentObjectRotation = grabbedProperties.rotation;
|
||||
this.currentObjectTime = now;
|
||||
this.currentCameraOrientation = Camera.orientation;
|
||||
|
||||
this.grabRadius = this.grabbedDistance;
|
||||
this.grabRadialVelocity = 0.0;
|
||||
|
||||
// offset between controller vector at the grab radius and the entity position
|
||||
var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||
targetPosition = Vec3.sum(targetPosition, worldControllerPosition);
|
||||
this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition);
|
||||
|
||||
// compute a constant based on the initial conditions which we use below to exaggerate hand motion
|
||||
// onto the held object
|
||||
this.radiusScalar = Math.log(this.grabRadius + 1.0);
|
||||
if (this.radiusScalar < 1.0) {
|
||||
this.radiusScalar = 1.0;
|
||||
}
|
||||
|
||||
// compute the mass for the purpose of energy and how quickly to move object
|
||||
this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density);
|
||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position));
|
||||
var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject);
|
||||
this.linearTimeScale = timeScale;
|
||||
this.actionID = Entities.addAction("far-grab", this.grabbedThingID, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: timeScale,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: timeScale,
|
||||
tag: "far-grab-" + MyAvatar.sessionUUID,
|
||||
ttl: ACTION_TTL
|
||||
});
|
||||
if (this.actionID === Uuid.NULL) {
|
||||
this.actionID = null;
|
||||
}
|
||||
|
||||
if (this.actionID !== null) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.grabbedThingID, "startDistanceGrab", args);
|
||||
}
|
||||
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
this.previousRoomControllerPosition = roomControllerPosition;
|
||||
};
|
||||
|
||||
this.continueDistanceHolding = function(controllerData) {
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var worldControllerRotation = controllerLocation.orientation;
|
||||
|
||||
// also transform the position into room space
|
||||
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, DISPATCHER_PROPERTIES);
|
||||
var now = Date.now();
|
||||
var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds
|
||||
this.currentObjectTime = now;
|
||||
|
||||
// the action was set up when this.distanceHolding was called. update the targets.
|
||||
var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) *
|
||||
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
||||
if (radius < 1.0) {
|
||||
radius = 1.0;
|
||||
}
|
||||
|
||||
var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition);
|
||||
var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta);
|
||||
var handMoved = Vec3.multiply(worldHandDelta, radius);
|
||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.grabbedThingID, "continueDistanceGrab", args);
|
||||
|
||||
// Update radialVelocity
|
||||
var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime);
|
||||
var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition));
|
||||
var newRadialVelocity = Vec3.dot(lastVelocity, delta);
|
||||
|
||||
var VELOCITY_AVERAGING_TIME = 0.016;
|
||||
var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME;
|
||||
if (blendFactor < 0.0) {
|
||||
blendFactor = 0.0;
|
||||
} else if (blendFactor > 1.0) {
|
||||
blendFactor = 1.0;
|
||||
}
|
||||
this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity;
|
||||
|
||||
var RADIAL_GRAB_AMPLIFIER = 10.0;
|
||||
if (Math.abs(this.grabRadialVelocity) > 0.0) {
|
||||
this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime *
|
||||
this.grabRadius * RADIAL_GRAB_AMPLIFIER);
|
||||
}
|
||||
|
||||
// don't let grabRadius go all the way to zero, because it can't come back from that
|
||||
var MINIMUM_GRAB_RADIUS = 0.1;
|
||||
if (this.grabRadius < MINIMUM_GRAB_RADIUS) {
|
||||
this.grabRadius = MINIMUM_GRAB_RADIUS;
|
||||
}
|
||||
var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||
newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition);
|
||||
newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);
|
||||
|
||||
// XXX
|
||||
// this.maybeScale(grabbedProperties);
|
||||
|
||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
||||
|
||||
this.linearTimeScale = (this.linearTimeScale / 2);
|
||||
if (this.linearTimeScale <= DISTANCE_HOLDING_ACTION_TIMEFRAME) {
|
||||
this.linearTimeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME;
|
||||
}
|
||||
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
||||
targetPosition: newTargetPosition,
|
||||
linearTimeScale: this.linearTimeScale,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
|
||||
ttl: ACTION_TTL
|
||||
});
|
||||
if (!success) {
|
||||
print("continueDistanceHolding -- updateAction failed: " + this.actionID);
|
||||
this.actionID = null;
|
||||
}
|
||||
|
||||
this.previousRoomControllerPosition = roomControllerPosition;
|
||||
};
|
||||
|
||||
this.endFarGrabAction = function () {
|
||||
ensureDynamic(this.grabbedThingID);
|
||||
this.distanceHolding = false;
|
||||
this.distanceRotating = false;
|
||||
Entities.deleteAction(this.grabbedThingID, this.actionID);
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args);
|
||||
if (this.targetObject) {
|
||||
this.targetObject.restoreTargetEntityOriginalProps();
|
||||
}
|
||||
this.actionID = null;
|
||||
this.grabbedThingID = null;
|
||||
this.targetObject = null;
|
||||
this.potentialEntityWithContextOverlay = false;
|
||||
};
|
||||
|
||||
this.updateRecommendedArea = function() {
|
||||
var dims = Controller.getViewportDimensions();
|
||||
this.reticleMaxX = dims.x - MARGIN;
|
||||
this.reticleMaxY = dims.y - MARGIN;
|
||||
};
|
||||
|
||||
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.restoreIgnoredEntities = function() {
|
||||
for (var i = 0; i < this.ignoredEntities.length; i++) {
|
||||
var data = {
|
||||
action: 'remove',
|
||||
id: this.ignoredEntities[i]
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data));
|
||||
}
|
||||
this.ignoredEntities = [];
|
||||
};
|
||||
|
||||
this.notPointingAtEntity = function(controllerData) {
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES);
|
||||
var entityType = entityProperty.type;
|
||||
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
|
||||
if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") ||
|
||||
intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.distanceRotate = function(otherFarGrabModule) {
|
||||
this.distanceRotating = true;
|
||||
this.distanceHolding = false;
|
||||
|
||||
var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation;
|
||||
var controllerRotationDelta =
|
||||
Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation));
|
||||
// Rotate entity by twice the delta rotation.
|
||||
controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta);
|
||||
|
||||
// Perform the rotation in the translation controller's action update.
|
||||
otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta,
|
||||
otherFarGrabModule.currentObjectRotation);
|
||||
|
||||
this.previousWorldControllerRotation = worldControllerRotation;
|
||||
};
|
||||
|
||||
this.prepareDistanceRotatingData = function(controllerData) {
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
|
||||
var controllerLocation = getControllerWorldLocation(this.handToController(), true);
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var worldControllerRotation = controllerLocation.orientation;
|
||||
|
||||
var grabbedProperties = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES);
|
||||
this.currentObjectPosition = grabbedProperties.position;
|
||||
this.grabRadius = intersection.distance;
|
||||
|
||||
// Offset between controller vector at the grab radius and the entity position.
|
||||
var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||
targetPosition = Vec3.sum(targetPosition, worldControllerPosition);
|
||||
this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition);
|
||||
|
||||
// Initial controller rotation.
|
||||
this.previousWorldControllerRotation = worldControllerRotation;
|
||||
};
|
||||
|
||||
this.destroyContextOverlay = function(controllerData) {
|
||||
if (this.entityWithContextOverlay) {
|
||||
ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay);
|
||||
this.entityWithContextOverlay = false;
|
||||
this.potentialEntityWithContextOverlay = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.targetIsNull = function() {
|
||||
var properties = Entities.getEntityProperties(this.grabbedThingID, DISPATCHER_PROPERTIES);
|
||||
if (Object.keys(properties).length === 0 && this.distanceHolding) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
if (HMD.active) {
|
||||
if (this.notPointingAtEntity(controllerData)) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
this.distanceHolding = false;
|
||||
this.distanceRotating = false;
|
||||
|
||||
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
||||
this.prepareDistanceRotatingData(controllerData);
|
||||
return makeRunningValues(true, [], []);
|
||||
} else {
|
||||
this.destroyContextOverlay();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
if (intersection.type === Picks.INTERSECTED_ENTITY && !Window.isPhysicsEnabled()) {
|
||||
// add to ignored items.
|
||||
if (this.ignoredEntities.indexOf(intersection.objectID) === -1) {
|
||||
var data = {
|
||||
action: 'add',
|
||||
id: intersection.objectID
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data));
|
||||
this.ignoredEntities.push(intersection.objectID);
|
||||
}
|
||||
}
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
|
||||
(this.notPointingAtEntity(controllerData) && Window.isPhysicsEnabled()) || this.targetIsNull()) {
|
||||
this.endFarGrabAction();
|
||||
this.restoreIgnoredEntities();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
this.intersectionDistance = controllerData.rayPicks[this.hand].distance;
|
||||
|
||||
var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";
|
||||
var otherFarGrabModule = getEnabledModuleByName(otherModuleName);
|
||||
|
||||
// gather up the readiness of the near-grab modules
|
||||
var nearGrabNames = [
|
||||
this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar",
|
||||
this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity",
|
||||
this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity",
|
||||
this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity",
|
||||
this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay",
|
||||
this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"
|
||||
];
|
||||
|
||||
var nearGrabReadiness = [];
|
||||
for (var i = 0; i < nearGrabNames.length; i++) {
|
||||
var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]);
|
||||
var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
nearGrabReadiness.push(ready);
|
||||
}
|
||||
|
||||
if (this.actionID) {
|
||||
// if we are doing a distance grab and the object or tablet gets close enough to the controller,
|
||||
// stop the far-grab so the near-grab or equip can take over.
|
||||
for (var k = 0; k < nearGrabReadiness.length; k++) {
|
||||
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID ||
|
||||
HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) {
|
||||
this.endFarGrabAction();
|
||||
this.restoreIgnoredEntities();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
this.continueDistanceHolding(controllerData);
|
||||
} else {
|
||||
// if we are doing a distance search and this controller moves into a position
|
||||
// where it could near-grab something, stop searching.
|
||||
for (var j = 0; j < nearGrabReadiness.length; j++) {
|
||||
if (nearGrabReadiness[j].active) {
|
||||
this.endFarGrabAction();
|
||||
this.restoreIgnoredEntities();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
var rayPickInfo = controllerData.rayPicks[this.hand];
|
||||
if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) {
|
||||
if (controllerData.triggerClicks[this.hand]) {
|
||||
var entityID = rayPickInfo.objectID;
|
||||
var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
||||
if (targetProps.href !== "") {
|
||||
AddressManager.handleLookupString(targetProps.href);
|
||||
this.restoreIgnoredEntities();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
this.targetObject = new TargetObject(entityID, targetProps);
|
||||
this.targetObject.parentProps = getEntityParents(targetProps);
|
||||
|
||||
if (this.contextOverlayTimer) {
|
||||
Script.clearTimeout(this.contextOverlayTimer);
|
||||
}
|
||||
this.contextOverlayTimer = false;
|
||||
if (entityID === this.entityWithContextOverlay) {
|
||||
this.destroyContextOverlay();
|
||||
} else {
|
||||
Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID);
|
||||
}
|
||||
|
||||
var targetEntity = this.targetObject.getTargetEntity();
|
||||
entityID = targetEntity.id;
|
||||
targetProps = targetEntity.props;
|
||||
|
||||
if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) {
|
||||
if (!entityIsDistanceGrabbable(targetProps)) {
|
||||
this.targetObject.makeDynamic();
|
||||
}
|
||||
|
||||
if (!this.distanceRotating) {
|
||||
this.grabbedThingID = entityID;
|
||||
this.grabbedDistance = rayPickInfo.distance;
|
||||
}
|
||||
|
||||
if (otherFarGrabModule.grabbedThingID === this.grabbedThingID &&
|
||||
otherFarGrabModule.distanceHolding) {
|
||||
this.prepareDistanceRotatingData(controllerData);
|
||||
this.distanceRotate(otherFarGrabModule);
|
||||
} else {
|
||||
this.distanceHolding = true;
|
||||
this.distanceRotating = false;
|
||||
this.startFarGrabAction(controllerData, targetProps);
|
||||
}
|
||||
}
|
||||
} else if (!this.entityWithContextOverlay) {
|
||||
var _this = this;
|
||||
|
||||
if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
|
||||
if (_this.contextOverlayTimer) {
|
||||
Script.clearTimeout(_this.contextOverlayTimer);
|
||||
}
|
||||
_this.contextOverlayTimer = false;
|
||||
_this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
|
||||
}
|
||||
|
||||
if (!_this.contextOverlayTimer) {
|
||||
_this.contextOverlayTimer = Script.setTimeout(function () {
|
||||
if (!_this.entityWithContextOverlay &&
|
||||
_this.contextOverlayTimer &&
|
||||
_this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
|
||||
var props = Entities.getEntityProperties(rayPickInfo.objectID, DISPATCHER_PROPERTIES);
|
||||
var pointerEvent = {
|
||||
type: "Move",
|
||||
id: _this.hand + 1, // 0 is reserved for hardware mouse
|
||||
pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
|
||||
rayPickInfo.intersection, props),
|
||||
pos3D: rayPickInfo.intersection,
|
||||
normal: rayPickInfo.surfaceNormal,
|
||||
direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
|
||||
button: "Secondary"
|
||||
};
|
||||
if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
|
||||
_this.entityWithContextOverlay = rayPickInfo.objectID;
|
||||
}
|
||||
}
|
||||
_this.contextOverlayTimer = false;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
} else if (this.distanceRotating) {
|
||||
this.distanceRotate(otherFarGrabModule);
|
||||
}
|
||||
}
|
||||
return this.exitIfDisabled(controllerData);
|
||||
};
|
||||
|
||||
this.exitIfDisabled = function(controllerData) {
|
||||
var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules";
|
||||
var disableModule = getEnabledModuleByName(moduleName);
|
||||
if (disableModule) {
|
||||
if (disableModule.disableModules) {
|
||||
this.endFarGrabAction();
|
||||
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
|
||||
this.highlightedEntity);
|
||||
this.highlightedEntity = null;
|
||||
this.restoreIgnoredEntities();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null;
|
||||
var offset = this.calculateOffset(controllerData);
|
||||
var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset);
|
||||
return makeRunningValues(true, [], [], laserLockInfo);
|
||||
};
|
||||
|
||||
this.calculateOffset = function(controllerData) {
|
||||
if (this.distanceHolding || this.distanceRotating) {
|
||||
var targetProps = Entities.getEntityProperties(this.targetObject.entityID,
|
||||
[ "position", "rotation", "registrationPoint", "dimensions" ]);
|
||||
return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
var leftFarActionGrabEntity = new FarActionGrabEntity(LEFT_HAND);
|
||||
var rightFarActionGrabEntity = new FarActionGrabEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftFarActionGrabEntity", leftFarActionGrabEntity);
|
||||
enableDispatcherModule("RightFarActionGrabEntity", rightFarActionGrabEntity);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftFarActionGrabEntity");
|
||||
disableDispatcherModule("RightFarActionGrabEntity");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,585 @@
|
|||
"use strict";
|
||||
|
||||
// farGrabEntity.js
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* jslint bitwise: true */
|
||||
|
||||
/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues,
|
||||
Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC,
|
||||
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC,
|
||||
projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager,
|
||||
getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGrabbableGroupParent,
|
||||
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function () {
|
||||
var MARGIN = 25;
|
||||
|
||||
function TargetObject(entityID, entityProps) {
|
||||
this.entityID = entityID;
|
||||
this.entityProps = entityProps;
|
||||
this.targetEntityID = null;
|
||||
this.targetEntityProps = null;
|
||||
|
||||
this.getTargetEntity = function () {
|
||||
var parentPropsLength = this.parentProps.length;
|
||||
if (parentPropsLength !== 0) {
|
||||
var targetEntity = {
|
||||
id: this.parentProps[parentPropsLength - 1].id,
|
||||
props: this.parentProps[parentPropsLength - 1]
|
||||
};
|
||||
this.targetEntityID = targetEntity.id;
|
||||
this.targetEntityProps = targetEntity.props;
|
||||
return targetEntity;
|
||||
}
|
||||
this.targetEntityID = this.entityID;
|
||||
this.targetEntityProps = this.entityProps;
|
||||
return {
|
||||
id: this.entityID,
|
||||
props: this.entityProps
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function FarGrabEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.grabbing = false;
|
||||
this.targetEntityID = null;
|
||||
this.targetObject = null;
|
||||
this.previouslyUnhooked = {};
|
||||
this.potentialEntityWithContextOverlay = false;
|
||||
this.entityWithContextOverlay = false;
|
||||
this.contextOverlayTimer = false;
|
||||
this.reticleMinX = MARGIN;
|
||||
this.reticleMaxX = 0;
|
||||
this.reticleMinY = MARGIN;
|
||||
this.reticleMaxY = 0;
|
||||
this.endedGrab = 0;
|
||||
this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms
|
||||
this.disabled = false;
|
||||
var _this = this;
|
||||
this.initialControllerRotation = Quat.IDENTITY;
|
||||
this.currentControllerRotation = Quat.IDENTITY;
|
||||
this.manipulating = false;
|
||||
this.wasManipulating = false;
|
||||
|
||||
var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX
|
||||
|
||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object
|
||||
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||
var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified
|
||||
var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
540,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(this.hand, false));
|
||||
|
||||
this.getOtherModule = function () {
|
||||
return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("LeftFarGrabEntity") : ("RightFarGrabEntity"));
|
||||
};
|
||||
|
||||
// Get the rotation of the fargrabbed entity.
|
||||
this.getTargetRotation = function () {
|
||||
if (this.targetIsNull()) {
|
||||
return null;
|
||||
} else {
|
||||
var props = Entities.getEntityProperties(this.targetEntityID, ["rotation"]);
|
||||
return props.rotation;
|
||||
}
|
||||
};
|
||||
|
||||
this.getOffhand = function () {
|
||||
return (this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND);
|
||||
}
|
||||
|
||||
// Activation criteria for rotating a fargrabbed entity. If we're changing the mapping, this is where to do it.
|
||||
this.shouldManipulateTarget = function (controllerData) {
|
||||
return (controllerData.triggerValues[this.getOffhand()] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.getOffhand()] > TRIGGER_ON_VALUE) ? true : false;
|
||||
};
|
||||
|
||||
// Get the delta between the current rotation and where the controller was when manipulation started.
|
||||
this.calculateEntityRotationManipulation = function (controllerRotation) {
|
||||
return Quat.multiply(controllerRotation, Quat.inverse(this.initialControllerRotation));
|
||||
};
|
||||
|
||||
this.setJointTranslation = function (newTargetPosLocal) {
|
||||
MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal);
|
||||
};
|
||||
|
||||
this.setJointRotation = function (newTargetRotLocal) {
|
||||
MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal);
|
||||
};
|
||||
|
||||
this.setJointRotation = function (newTargetRotLocal) {
|
||||
MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal);
|
||||
};
|
||||
|
||||
this.handToController = function () {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
};
|
||||
|
||||
this.distanceGrabTimescale = function (mass, distance) {
|
||||
var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass /
|
||||
DISTANCE_HOLDING_UNITY_MASS * distance /
|
||||
DISTANCE_HOLDING_UNITY_DISTANCE;
|
||||
if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) {
|
||||
timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME;
|
||||
}
|
||||
return timeScale;
|
||||
};
|
||||
|
||||
this.getMass = function (dimensions, density) {
|
||||
return (dimensions.x * dimensions.y * dimensions.z) * density;
|
||||
};
|
||||
|
||||
this.startFarGrabEntity = function (controllerData, targetProps) {
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var worldControllerRotation = controllerLocation.orientation;
|
||||
// transform the position into room space
|
||||
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
// add the action and initialize some variables
|
||||
this.currentObjectPosition = targetProps.position;
|
||||
this.currentObjectRotation = targetProps.rotation;
|
||||
this.currentObjectTime = now;
|
||||
|
||||
this.grabRadius = this.grabbedDistance;
|
||||
this.grabRadialVelocity = 0.0;
|
||||
|
||||
// offset between controller vector at the grab radius and the entity position
|
||||
var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||
targetPosition = Vec3.sum(targetPosition, worldControllerPosition);
|
||||
this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition);
|
||||
|
||||
// compute a constant based on the initial conditions which we use below to exaggerate hand motion
|
||||
// onto the held object
|
||||
this.radiusScalar = Math.log(this.grabRadius + 1.0);
|
||||
if (this.radiusScalar < 1.0) {
|
||||
this.radiusScalar = 1.0;
|
||||
}
|
||||
|
||||
// compute the mass for the purpose of energy and how quickly to move object
|
||||
this.mass = this.getMass(targetProps.dimensions, targetProps.density);
|
||||
|
||||
// Debounce haptic pules. Can occur as near grab controller module vacillates between being ready or not due to
|
||||
// changing positions and floating point rounding.
|
||||
if (Date.now() - this.endedGrab > this.MIN_HAPTIC_PULSE_INTERVAL) {
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
}
|
||||
|
||||
unhighlightTargetEntity(this.targetEntityID);
|
||||
var message = {
|
||||
hand: this.hand,
|
||||
entityID: this.targetEntityID
|
||||
};
|
||||
|
||||
Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
|
||||
|
||||
var newTargetPosLocal = MyAvatar.worldToJointPoint(targetProps.position);
|
||||
var newTargetRotLocal = targetProps.rotation;
|
||||
this.setJointTranslation(newTargetPosLocal);
|
||||
this.setJointRotation(newTargetRotLocal);
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(targetProps.id, "startDistanceGrab", args);
|
||||
|
||||
this.targetEntityID = targetProps.id;
|
||||
|
||||
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
}
|
||||
var farJointIndex = FAR_GRAB_JOINTS[this.hand];
|
||||
this.grabID = MyAvatar.grab(targetProps.id, farJointIndex,
|
||||
Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex),
|
||||
Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex));
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'grab',
|
||||
grabbedEntity: targetProps.id,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
this.grabbing = true;
|
||||
|
||||
this.previousRoomControllerPosition = roomControllerPosition;
|
||||
};
|
||||
|
||||
this.continueDistanceHolding = function (controllerData) {
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var worldControllerRotation = controllerLocation.orientation;
|
||||
|
||||
// also transform the position into room space
|
||||
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||
|
||||
var targetProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||
var now = Date.now();
|
||||
var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds
|
||||
this.currentObjectTime = now;
|
||||
|
||||
// the action was set up when this.distanceHolding was called. update the targets.
|
||||
var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) *
|
||||
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
||||
if (radius < 1.0) {
|
||||
radius = 1.0;
|
||||
}
|
||||
|
||||
var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition);
|
||||
var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta);
|
||||
var handMoved = Vec3.multiply(worldHandDelta, radius);
|
||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args);
|
||||
|
||||
// Update radialVelocity
|
||||
var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime);
|
||||
var delta = Vec3.normalize(Vec3.subtract(targetProps.position, worldControllerPosition));
|
||||
var newRadialVelocity = Vec3.dot(lastVelocity, delta);
|
||||
|
||||
var VELOCITY_AVERAGING_TIME = 0.016;
|
||||
var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME;
|
||||
if (blendFactor < 0.0) {
|
||||
blendFactor = 0.0;
|
||||
} else if (blendFactor > 1.0) {
|
||||
blendFactor = 1.0;
|
||||
}
|
||||
this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity;
|
||||
|
||||
var RADIAL_GRAB_AMPLIFIER = 10.0;
|
||||
if (Math.abs(this.grabRadialVelocity) > 0.0) {
|
||||
this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime *
|
||||
this.grabRadius * RADIAL_GRAB_AMPLIFIER);
|
||||
}
|
||||
|
||||
// don't let grabRadius go all the way to zero, because it can't come back from that
|
||||
var MINIMUM_GRAB_RADIUS = 0.1;
|
||||
if (this.grabRadius < MINIMUM_GRAB_RADIUS) {
|
||||
this.grabRadius = MINIMUM_GRAB_RADIUS;
|
||||
}
|
||||
var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||
newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition);
|
||||
newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);
|
||||
|
||||
var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition);
|
||||
|
||||
// This block handles the user's ability to rotate the object they're FarGrabbing
|
||||
if (this.shouldManipulateTarget(controllerData)) {
|
||||
// Get the pose of the controller that is not grabbing.
|
||||
var pose = Controller.getPoseValue((this.getOffhand() ? Controller.Standard.RightHand : Controller.Standard.LeftHand));
|
||||
if (pose.valid) {
|
||||
// If we weren't manipulating the object yet, initialize the entity's original position.
|
||||
if (!this.manipulating) {
|
||||
// This will only be triggered if we've let go of the off-hand trigger and pulled it again without ending a grab.
|
||||
// Need to poll the entity's rotation again here.
|
||||
if (!this.wasManipulating) {
|
||||
this.initialEntityRotation = this.getTargetRotation();
|
||||
}
|
||||
// Save the original controller orientation, we only care about the delta between this rotation and wherever
|
||||
// the controller rotates, so that we can apply it to the entity's rotation.
|
||||
this.initialControllerRotation = Quat.multiply(pose.rotation, MyAvatar.orientation);
|
||||
this.manipulating = true;
|
||||
}
|
||||
}
|
||||
|
||||
var rot = Quat.multiply(pose.rotation, MyAvatar.orientation);
|
||||
var rotBetween = this.calculateEntityRotationManipulation(rot);
|
||||
var doubleRot = Quat.multiply(rotBetween, rotBetween);
|
||||
this.lastJointRotation = Quat.multiply(doubleRot, this.initialEntityRotation);
|
||||
this.setJointRotation(this.lastJointRotation);
|
||||
} else {
|
||||
// If we were manipulating but the user isn't currently expressing this intent, we want to know so we preserve the rotation
|
||||
// between manipulations without ending the fargrab.
|
||||
if (this.manipulating) {
|
||||
this.initialEntityRotation = this.lastJointRotation;
|
||||
this.wasManipulating = true;
|
||||
}
|
||||
this.manipulating = false;
|
||||
// Reset the inital controller position.
|
||||
this.initialControllerRotation = Quat.IDENTITY;
|
||||
}
|
||||
this.setJointTranslation(newTargetPosLocal);
|
||||
|
||||
this.previousRoomControllerPosition = roomControllerPosition;
|
||||
};
|
||||
|
||||
this.endFarGrabEntity = function (controllerData) {
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
this.grabID = null;
|
||||
}
|
||||
|
||||
this.endedGrab = Date.now();
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args);
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'release',
|
||||
grabbedEntity: this.targetEntityID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
unhighlightTargetEntity(this.targetEntityID);
|
||||
this.grabbing = false;
|
||||
this.potentialEntityWithContextOverlay = false;
|
||||
MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]);
|
||||
this.initialEntityRotation = Quat.IDENTITY;
|
||||
this.initialControllerRotation = Quat.IDENTITY;
|
||||
this.targetEntityID = null;
|
||||
this.manipulating = false;
|
||||
this.wasManipulating = false;
|
||||
var otherModule = this.getOtherModule();
|
||||
otherModule.disabled = false;
|
||||
};
|
||||
|
||||
this.updateRecommendedArea = function () {
|
||||
var dims = Controller.getViewportDimensions();
|
||||
this.reticleMaxX = dims.x - MARGIN;
|
||||
this.reticleMaxY = dims.y - MARGIN;
|
||||
};
|
||||
|
||||
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.notPointingAtEntity = function (controllerData) {
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES);
|
||||
var entityType = entityProperty.type;
|
||||
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
|
||||
if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") ||
|
||||
intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.destroyContextOverlay = function (controllerData) {
|
||||
if (this.entityWithContextOverlay) {
|
||||
ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay);
|
||||
this.entityWithContextOverlay = false;
|
||||
this.potentialEntityWithContextOverlay = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.targetIsNull = function () {
|
||||
var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||
if (Object.keys(properties).length === 0 && this.distanceHolding) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.getTargetProps = function (controllerData) {
|
||||
var targetEntity = controllerData.rayPicks[this.hand].objectID;
|
||||
if (targetEntity) {
|
||||
var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);
|
||||
if (entityIsGrabbable(gtProps)) {
|
||||
// if we've attempted to grab a child, roll up to the root of the tree
|
||||
var groupRootProps = findGrabbableGroupParent(controllerData, gtProps);
|
||||
if (entityIsGrabbable(groupRootProps)) {
|
||||
return groupRootProps;
|
||||
}
|
||||
return gtProps;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
if (HMD.active) {
|
||||
if (this.notPointingAtEntity(controllerData)) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
this.distanceHolding = false;
|
||||
|
||||
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.disabled) {
|
||||
var otherModule = this.getOtherModule();
|
||||
otherModule.disabled = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
} else {
|
||||
this.destroyContextOverlay();
|
||||
}
|
||||
}
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) {
|
||||
this.endFarGrabEntity(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
this.intersectionDistance = controllerData.rayPicks[this.hand].distance;
|
||||
|
||||
// gather up the readiness of the near-grab modules
|
||||
var nearGrabNames = [
|
||||
this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar",
|
||||
this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity",
|
||||
this.hand === RIGHT_HAND ? "RightNearGrabEntity" : "LeftNearGrabEntity"
|
||||
];
|
||||
if (!this.grabbing) {
|
||||
nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay");
|
||||
nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight");
|
||||
}
|
||||
|
||||
var nearGrabReadiness = [];
|
||||
for (var i = 0; i < nearGrabNames.length; i++) {
|
||||
var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]);
|
||||
var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
nearGrabReadiness.push(ready);
|
||||
}
|
||||
|
||||
if (this.targetEntityID) {
|
||||
// if we are doing a distance grab and the object gets close enough to the controller,
|
||||
// stop the far-grab so the near-grab or equip can take over.
|
||||
for (var k = 0; k < nearGrabReadiness.length; k++) {
|
||||
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID)) {
|
||||
this.endFarGrabEntity(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
this.continueDistanceHolding(controllerData);
|
||||
} else {
|
||||
// if we are doing a distance search and this controller moves into a position
|
||||
// where it could near-grab something, stop searching.
|
||||
for (var j = 0; j < nearGrabReadiness.length; j++) {
|
||||
if (nearGrabReadiness[j].active) {
|
||||
this.endFarGrabEntity(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
var rayPickInfo = controllerData.rayPicks[this.hand];
|
||||
if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) {
|
||||
if (controllerData.triggerClicks[this.hand]) {
|
||||
var entityID = rayPickInfo.objectID;
|
||||
var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
||||
if (targetProps.href !== "") {
|
||||
AddressManager.handleLookupString(targetProps.href);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
this.targetObject = new TargetObject(entityID, targetProps);
|
||||
this.targetObject.parentProps = getEntityParents(targetProps);
|
||||
|
||||
if (this.contextOverlayTimer) {
|
||||
Script.clearTimeout(this.contextOverlayTimer);
|
||||
}
|
||||
this.contextOverlayTimer = false;
|
||||
if (entityID === this.entityWithContextOverlay) {
|
||||
this.destroyContextOverlay();
|
||||
} else {
|
||||
Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID);
|
||||
}
|
||||
|
||||
var targetEntity = this.targetObject.getTargetEntity();
|
||||
entityID = targetEntity.id;
|
||||
targetProps = targetEntity.props;
|
||||
|
||||
if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) {
|
||||
|
||||
this.targetEntityID = entityID;
|
||||
this.grabbedDistance = rayPickInfo.distance;
|
||||
this.distanceHolding = true;
|
||||
this.startFarGrabEntity(controllerData, targetProps);
|
||||
}
|
||||
} else if (!this.entityWithContextOverlay) {
|
||||
var _this = this;
|
||||
|
||||
if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
|
||||
if (_this.contextOverlayTimer) {
|
||||
Script.clearTimeout(_this.contextOverlayTimer);
|
||||
}
|
||||
_this.contextOverlayTimer = false;
|
||||
_this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
|
||||
}
|
||||
|
||||
if (!_this.contextOverlayTimer) {
|
||||
_this.contextOverlayTimer = Script.setTimeout(function () {
|
||||
if (!_this.entityWithContextOverlay &&
|
||||
_this.contextOverlayTimer &&
|
||||
_this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
|
||||
var cotProps = Entities.getEntityProperties(rayPickInfo.objectID,
|
||||
DISPATCHER_PROPERTIES);
|
||||
var pointerEvent = {
|
||||
type: "Move",
|
||||
id: _this.hand + 1, // 0 is reserved for hardware mouse
|
||||
pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
|
||||
rayPickInfo.intersection, cotProps),
|
||||
pos3D: rayPickInfo.intersection,
|
||||
normal: rayPickInfo.surfaceNormal,
|
||||
direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
|
||||
button: "Secondary"
|
||||
};
|
||||
if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
|
||||
_this.entityWithContextOverlay = rayPickInfo.objectID;
|
||||
}
|
||||
}
|
||||
_this.contextOverlayTimer = false;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.exitIfDisabled(controllerData);
|
||||
};
|
||||
|
||||
this.exitIfDisabled = function (controllerData) {
|
||||
var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules";
|
||||
var disableModule = getEnabledModuleByName(moduleName);
|
||||
if (disableModule) {
|
||||
if (disableModule.disableModules) {
|
||||
this.endFarGrabEntity(controllerData);
|
||||
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
|
||||
this.highlightedEntity = null;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
var grabbedThing = this.distanceHolding ? this.targetObject.entityID : null;
|
||||
var offset = this.calculateOffset(controllerData);
|
||||
var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset);
|
||||
return makeRunningValues(true, [], [], laserLockInfo);
|
||||
};
|
||||
|
||||
this.calculateOffset = function (controllerData) {
|
||||
if (this.distanceHolding) {
|
||||
var targetProps = Entities.getEntityProperties(this.targetObject.entityID,
|
||||
["position", "rotation", "registrationPoint", "dimensions"]);
|
||||
return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
var leftFarGrabEntity = new FarGrabEntity(LEFT_HAND);
|
||||
var rightFarGrabEntity = new FarGrabEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftFarGrabEntity", leftFarGrabEntity);
|
||||
enableDispatcherModule("RightFarGrabEntity", rightFarGrabEntity);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftFarGrabEntity");
|
||||
disableDispatcherModule("RightFarGrabEntity");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,102 @@
|
|||
"use strict";
|
||||
|
||||
// farTrigger.js
|
||||
//
|
||||
// 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, RIGHT_HAND, LEFT_HAND, MyAvatar,
|
||||
makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters,
|
||||
getGrabbableData, makeLaserParams, DISPATCHER_PROPERTIES
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() {
|
||||
function entityWantsFarTrigger(props) {
|
||||
var grabbableData = getGrabbableData(props);
|
||||
return grabbableData.triggerable;
|
||||
}
|
||||
|
||||
function FarTriggerEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.targetEntityID = null;
|
||||
this.grabbing = false;
|
||||
this.previousParentID = {};
|
||||
this.previousParentJointIndex = {};
|
||||
this.previouslyUnhooked = {};
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
520,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(this.hand, false));
|
||||
|
||||
this.getTargetProps = function (controllerData) {
|
||||
var targetEntity = controllerData.rayPicks[this.hand].objectID;
|
||||
if (targetEntity && controllerData.rayPicks[this.hand].type === RayPick.INTERSECTED_ENTITY) {
|
||||
var targetProperties = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);
|
||||
if (entityWantsFarTrigger(targetProperties)) {
|
||||
return targetProperties;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.startFarTrigger = function (controllerData) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "startFarTrigger", args);
|
||||
};
|
||||
|
||||
this.continueFarTrigger = function (controllerData) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueFarTrigger", args);
|
||||
};
|
||||
|
||||
this.endFarTrigger = function (controllerData) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "stopFarTrigger", args);
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
this.targetEntityID = null;
|
||||
if (controllerData.triggerClicks[this.hand] === 0) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var targetProps = this.getTargetProps(controllerData);
|
||||
if (targetProps) {
|
||||
this.targetEntityID = targetProps.id;
|
||||
this.startFarTrigger(controllerData);
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
} else {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
var targetEntity = controllerData.rayPicks[this.hand].objectID;
|
||||
if (controllerData.triggerClicks[this.hand] === 0 || this.targetEntityID !== targetEntity) {
|
||||
this.endFarTrigger(controllerData);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
this.continueFarTrigger(controllerData);
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftFarTriggerEntity = new FarTriggerEntity(LEFT_HAND);
|
||||
var rightFarTriggerEntity = new FarTriggerEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftFarTriggerEntity", leftFarTriggerEntity);
|
||||
enableDispatcherModule("RightFarTriggerEntity", rightFarTriggerEntity);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftFarTriggerEntity");
|
||||
disableDispatcherModule("RightFarTriggerEntity");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// 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, RIGHT_HAND, LEFT_HAND, HMD, makeLaserParams */
|
||||
(function() {
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
var ControllerDispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
var MARGIN = 25;
|
||||
var HUD_LASER_OFFSET = 2;
|
||||
function HudOverlayPointer(hand) {
|
||||
this.hand = hand;
|
||||
this.running = false;
|
||||
this.reticleMinX = MARGIN;
|
||||
this.reticleMaxX;
|
||||
this.reticleMinY = MARGIN;
|
||||
this.reticleMaxY;
|
||||
this.parameters = ControllerDispatcherUtils.makeDispatcherModuleParameters(
|
||||
160, // Same as webSurfaceLaserInput.
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams((this.hand + HUD_LASER_OFFSET), false));
|
||||
|
||||
this.getFarGrab = function () {
|
||||
return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("RightFarGrabEntity") : ("LeftFarGrabEntity"));
|
||||
}
|
||||
|
||||
this.farGrabActive = function () {
|
||||
var farGrab = this.getFarGrab();
|
||||
// farGrab will be null if module isn't loaded.
|
||||
if (farGrab) {
|
||||
return farGrab.targetIsNull();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.getOtherHandController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
|
||||
};
|
||||
|
||||
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.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.pointingAtTablet = function(controllerData) {
|
||||
var rayPick = controllerData.rayPicks[this.hand];
|
||||
return (HMD.tabletScreenID && HMD.homeButtonID && (rayPick.objectID === HMD.tabletScreenID || rayPick.objectID === HMD.homeButtonID));
|
||||
};
|
||||
|
||||
this.getOtherModule = function() {
|
||||
return this.hand === RIGHT_HAND ? leftHudOverlayPointer : rightHudOverlayPointer;
|
||||
};
|
||||
|
||||
this.processLaser = function(controllerData) {
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
if ((controllerData.triggerValues[this.hand] < ControllerDispatcherUtils.TRIGGER_ON_VALUE || !controllerLocation.valid) ||
|
||||
this.pointingAtTablet(controllerData)) {
|
||||
return false;
|
||||
}
|
||||
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
|
||||
if (!Window.isPointOnDesktopWindow(point2d) && !this.triggerClicked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.triggerClicked = controllerData.triggerClicks[this.hand];
|
||||
return true;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
var otherModuleRunning = this.getOtherModule().running;
|
||||
if (!otherModuleRunning && HMD.active && !this.farGrabActive()) {
|
||||
if (this.processLaser(controllerData)) {
|
||||
this.running = true;
|
||||
return ControllerDispatcherUtils.makeRunningValues(true, [], []);
|
||||
} else {
|
||||
this.running = false;
|
||||
return ControllerDispatcherUtils.makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
return ControllerDispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
return this.isReady(controllerData);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var leftHudOverlayPointer = new HudOverlayPointer(LEFT_HAND);
|
||||
var rightHudOverlayPointer = new HudOverlayPointer(RIGHT_HAND);
|
||||
|
||||
ControllerDispatcherUtils.enableDispatcherModule("LeftHudOverlayPointer", leftHudOverlayPointer);
|
||||
ControllerDispatcherUtils.enableDispatcherModule("RightHudOverlayPointer", rightHudOverlayPointer);
|
||||
|
||||
function cleanup() {
|
||||
ControllerDispatcherUtils.disableDispatcherModule("LeftHudOverlayPointer");
|
||||
ControllerDispatcherUtils.disableDispatcherModule("RightHudOverlayPointer");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
})();
|
|
@ -0,0 +1,253 @@
|
|||
"use strict";
|
||||
|
||||
// inEditMode.js
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* jslint bitwise: true */
|
||||
|
||||
/* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues,
|
||||
Messages, makeDispatcherModuleParameters, HMD, getEnabledModuleByName, TRIGGER_ON_VALUE, isInEditMode, Picks,
|
||||
makeLaserParams
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
Script.include("/~/system/libraries/utils.js");
|
||||
|
||||
(function () {
|
||||
var MARGIN = 25;
|
||||
function InEditMode(hand) {
|
||||
this.hand = hand;
|
||||
this.isEditing = false;
|
||||
this.triggerClicked = false;
|
||||
this.selectedTarget = null;
|
||||
this.reticleMinX = MARGIN;
|
||||
this.reticleMaxX = null;
|
||||
this.reticleMinY = MARGIN;
|
||||
this.reticleMaxY = null;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
165, // Lower priority than webSurfaceLaserInput and hudOverlayPointer.
|
||||
this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(this.hand, false));
|
||||
|
||||
this.nearTablet = function(overlays) {
|
||||
for (var i = 0; i < overlays.length; i++) {
|
||||
if (HMD.tabletID && overlays[i] === HMD.tabletID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.handToController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
};
|
||||
|
||||
this.pointingAtTablet = function(objectID) {
|
||||
return (HMD.tabletScreenID && objectID === HMD.tabletScreenID) ||
|
||||
(HMD.homeButtonID && objectID === HMD.homeButtonID);
|
||||
};
|
||||
|
||||
this.calculateNewReticlePosition = function(intersection) {
|
||||
var dims = Controller.getViewportDimensions();
|
||||
this.reticleMaxX = dims.x - MARGIN;
|
||||
this.reticleMaxY = dims.y - MARGIN;
|
||||
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.ENTITY_TOOL_UPDATES_CHANNEL = "entityToolUpdates";
|
||||
|
||||
this.sendPickData = function(controllerData) {
|
||||
if (controllerData.triggerClicks[this.hand]) {
|
||||
var hand = this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
if (!this.triggerClicked) {
|
||||
this.selectedTarget = controllerData.rayPicks[this.hand];
|
||||
if (!this.selectedTarget.intersects) {
|
||||
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
|
||||
method: "clearSelection",
|
||||
hand: hand
|
||||
}));
|
||||
} else {
|
||||
if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) {
|
||||
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
|
||||
method: "selectEntity",
|
||||
entityID: this.selectedTarget.objectID,
|
||||
hand: hand
|
||||
}));
|
||||
} else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) {
|
||||
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
|
||||
method: "selectOverlay",
|
||||
overlayID: this.selectedTarget.objectID,
|
||||
hand: hand
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.triggerClicked = true;
|
||||
}
|
||||
|
||||
this.sendPointingAtData(controllerData);
|
||||
};
|
||||
|
||||
this.sendPointingAtData = function(controllerData) {
|
||||
var rayPick = controllerData.rayPicks[this.hand];
|
||||
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
|
||||
var desktopWindow = Window.isPointOnDesktopWindow(point2d);
|
||||
var tablet = this.pointingAtTablet(rayPick.objectID);
|
||||
var rightHand = this.hand === RIGHT_HAND;
|
||||
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
|
||||
method: "pointingAt",
|
||||
desktopWindow: desktopWindow,
|
||||
tablet: tablet,
|
||||
rightHand: rightHand
|
||||
}));
|
||||
};
|
||||
|
||||
this.runModule = function() {
|
||||
return makeRunningValues(true, [], []);
|
||||
};
|
||||
|
||||
this.exitModule = function() {
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.isReady = function(controllerData) {
|
||||
if (isInEditMode()) {
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_ON_VALUE) {
|
||||
this.triggerClicked = false;
|
||||
}
|
||||
Messages.sendLocalMessage('Hifi-unhighlight-all', '');
|
||||
return this.runModule();
|
||||
}
|
||||
this.triggerClicked = false;
|
||||
return this.exitModule();
|
||||
};
|
||||
|
||||
this.run = function(controllerData) {
|
||||
|
||||
// Tablet stylus.
|
||||
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightTabletStylusInput" : "LeftTabletStylusInput");
|
||||
if (tabletStylusInput) {
|
||||
var tabletReady = tabletStylusInput.isReady(controllerData);
|
||||
if (tabletReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet surface.
|
||||
var webLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
|
||||
if (webLaser) {
|
||||
var webLaserReady = webLaser.isReady(controllerData);
|
||||
var target = controllerData.rayPicks[this.hand].objectID;
|
||||
this.sendPointingAtData(controllerData);
|
||||
if (webLaserReady.active && this.pointingAtTablet(target)) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// HUD overlay.
|
||||
if (!controllerData.triggerClicks[this.hand]) { // Don't grab if trigger pressed when laser starts intersecting.
|
||||
var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightHudOverlayPointer" : "LeftHudOverlayPointer");
|
||||
if (hudLaser) {
|
||||
var hudLaserReady = hudLaser.isReady(controllerData);
|
||||
if (hudLaserReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet highlight and grabbing.
|
||||
var tabletHighlight = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightNearTabletHighlight" : "LeftNearTabletHighlight");
|
||||
if (tabletHighlight) {
|
||||
var tabletHighlightReady = tabletHighlight.isReady(controllerData);
|
||||
if (tabletHighlightReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// Teleport.
|
||||
var teleport = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTeleporter" : "LeftTeleporter");
|
||||
if (teleport) {
|
||||
var teleportReady = teleport.isReady(controllerData);
|
||||
if (teleportReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)) {
|
||||
var stopRunning = false;
|
||||
controllerData.nearbyOverlayIDs[this.hand].forEach(function(overlayID) {
|
||||
var overlayName = Overlays.getProperty(overlayID, "name");
|
||||
if (overlayName === "KeyboardAnchor") {
|
||||
stopRunning = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (stopRunning) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
this.sendPickData(controllerData);
|
||||
return this.isReady(controllerData);
|
||||
};
|
||||
}
|
||||
|
||||
var leftHandInEditMode = new InEditMode(LEFT_HAND);
|
||||
var rightHandInEditMode = new InEditMode(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftHandInEditMode", leftHandInEditMode);
|
||||
enableDispatcherModule("RightHandInEditMode", rightHandInEditMode);
|
||||
|
||||
var INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status";
|
||||
var HAND_RAYPICK_BLACKLIST_CHANNEL = "Hifi-Hand-RayPick-Blacklist";
|
||||
this.handleMessage = function (channel, data, sender) {
|
||||
if (channel === INEDIT_STATUS_CHANNEL && sender === MyAvatar.sessionUUID) {
|
||||
var message;
|
||||
|
||||
try {
|
||||
message = JSON.parse(data);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.method) {
|
||||
case "editing":
|
||||
if (message.hand === LEFT_HAND) {
|
||||
leftHandInEditMode.isEditing = message.editing;
|
||||
} else {
|
||||
rightHandInEditMode.isEditing = message.editing;
|
||||
}
|
||||
Messages.sendLocalMessage(HAND_RAYPICK_BLACKLIST_CHANNEL, JSON.stringify({
|
||||
action: "tablet",
|
||||
hand: message.hand,
|
||||
blacklist: message.editing
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
Messages.subscribe(INEDIT_STATUS_CHANNEL);
|
||||
Messages.messageReceived.connect(this.handleMessage);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftHandInEditMode");
|
||||
disableDispatcherModule("RightHandInEditMode");
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,185 @@
|
|||
"use strict";
|
||||
|
||||
// inVREditMode.js
|
||||
//
|
||||
// Created by David Rowe on 16 Sep 2017.
|
||||
// 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, HMD, Messages, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
|
||||
makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName, makeLaserParams
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
(function () {
|
||||
|
||||
function InVREditMode(hand) {
|
||||
this.hand = hand;
|
||||
this.isAppActive = false;
|
||||
this.isEditing = false;
|
||||
this.running = false;
|
||||
var NO_HAND_LASER = -1; // Invalid hand parameter so that standard laser is not displayed.
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
166, // Slightly lower priority than inEditMode.
|
||||
this.hand === RIGHT_HAND
|
||||
? ["rightHand", "rightHandEquip", "rightHandTrigger"]
|
||||
: ["leftHand", "leftHandEquip", "leftHandTrigger"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(NO_HAND_LASER, false)
|
||||
);
|
||||
|
||||
this.pointingAtTablet = function (objectID) {
|
||||
return (HMD.tabletScreenID && objectID === HMD.tabletScreenID) ||
|
||||
(HMD.homeButtonID && objectID === HMD.homeButtonID);
|
||||
};
|
||||
|
||||
// The Shapes app has a non-standard laser: in particular, the laser end dot displays on its own when the laser is
|
||||
// pointing at the Shapes UI. The laser on/off is controlled by this module but the laser is implemented in the Shapes
|
||||
// app.
|
||||
// If, in the future, the Shapes app laser interaction is adopted as a standard UI style then the laser could be
|
||||
// implemented in the controller modules along side the other laser styles.
|
||||
var INVREDIT_MODULE_RUNNING = "Hifi-InVREdit-Module-Running";
|
||||
|
||||
this.runModule = function () {
|
||||
if (!this.running) {
|
||||
Messages.sendLocalMessage(INVREDIT_MODULE_RUNNING, JSON.stringify({
|
||||
hand: this.hand,
|
||||
running: true
|
||||
}));
|
||||
this.running = true;
|
||||
}
|
||||
return makeRunningValues(true, [], []);
|
||||
};
|
||||
|
||||
this.exitModule = function () {
|
||||
if (this.running) {
|
||||
Messages.sendLocalMessage(INVREDIT_MODULE_RUNNING, JSON.stringify({
|
||||
hand: this.hand,
|
||||
running: false
|
||||
}));
|
||||
this.running = false;
|
||||
}
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
if (this.isAppActive) {
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
// Default behavior if disabling is not enabled.
|
||||
if (!this.isAppActive) {
|
||||
return this.exitModule();
|
||||
}
|
||||
|
||||
// Tablet stylus.
|
||||
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightTabletStylusInput" : "LeftTabletStylusInput");
|
||||
if (tabletStylusInput) {
|
||||
var tabletReady = tabletStylusInput.isReady(controllerData);
|
||||
if (tabletReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet surface.
|
||||
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
|
||||
if (overlayLaser) {
|
||||
var overlayLaserReady = overlayLaser.isReady(controllerData);
|
||||
var target = controllerData.rayPicks[this.hand].objectID;
|
||||
if (overlayLaserReady.active && this.pointingAtTablet(target)) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet highlight and grabbing.
|
||||
var tabletHighlight = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightNearTabletHighlight" : "LeftNearTabletHighlight");
|
||||
if (tabletHighlight) {
|
||||
var tabletHighlightReady = tabletHighlight.isReady(controllerData);
|
||||
if (tabletHighlightReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// HUD overlay.
|
||||
if (!controllerData.triggerClicks[this.hand]) {
|
||||
var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightHudOverlayPointer" : "LeftHudOverlayPointer");
|
||||
if (hudLaser) {
|
||||
var hudLaserReady = hudLaser.isReady(controllerData);
|
||||
if (hudLaserReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Teleport.
|
||||
var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightTeleporter" : "LeftTeleporter");
|
||||
if (teleporter) {
|
||||
var teleporterReady = teleporter.isReady(controllerData);
|
||||
if (teleporterReady.active) {
|
||||
return this.exitModule();
|
||||
}
|
||||
}
|
||||
|
||||
// Other behaviors are disabled.
|
||||
return this.runModule();
|
||||
};
|
||||
}
|
||||
|
||||
var leftHandInVREditMode = new InVREditMode(LEFT_HAND);
|
||||
var rightHandInVREditMode = new InVREditMode(RIGHT_HAND);
|
||||
enableDispatcherModule("LeftHandInVREditMode", leftHandInVREditMode);
|
||||
enableDispatcherModule("RightHandInVREditMode", rightHandInVREditMode);
|
||||
|
||||
var INVREDIT_STATUS_CHANNEL = "Hifi-InVREdit-Status";
|
||||
var HAND_RAYPICK_BLACKLIST_CHANNEL = "Hifi-Hand-RayPick-Blacklist";
|
||||
this.handleMessage = function (channel, data, sender) {
|
||||
if (channel === INVREDIT_STATUS_CHANNEL && sender === MyAvatar.sessionUUID) {
|
||||
var message;
|
||||
|
||||
try {
|
||||
message = JSON.parse(data);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.method) {
|
||||
case "active":
|
||||
leftHandInVREditMode.isAppActive = message.active;
|
||||
rightHandInVREditMode.isAppActive = message.active;
|
||||
break;
|
||||
case "editing":
|
||||
if (message.hand === LEFT_HAND) {
|
||||
leftHandInVREditMode.isEditing = message.editing;
|
||||
} else {
|
||||
rightHandInVREditMode.isEditing = message.editing;
|
||||
}
|
||||
Messages.sendLocalMessage(HAND_RAYPICK_BLACKLIST_CHANNEL, JSON.stringify({
|
||||
action: "tablet",
|
||||
hand: message.hand,
|
||||
blacklist: message.editing
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
Messages.subscribe(INVREDIT_STATUS_CHANNEL);
|
||||
Messages.messageReceived.connect(this.handleMessage);
|
||||
|
||||
this.cleanup = function () {
|
||||
disableDispatcherModule("LeftHandInVREditMode");
|
||||
disableDispatcherModule("RightHandInVREditMode");
|
||||
};
|
||||
Script.scriptEnding.connect(this.cleanup);
|
||||
}());
|
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
/* global Script, HMD, Reticle, Vec3, Controller */
|
||||
|
||||
(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.hmdWasActive = HMD.active;
|
||||
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();
|
||||
var hmdChanged = this.hmdWasActive !== HMD.active;
|
||||
this.hmdWasActive = HMD.active;
|
||||
this.triggersPressed(controllerData, now);
|
||||
if (HMD.active) {
|
||||
if (!this.mouseActivity.expired(now) && _this.handControllerActivity.expired()) {
|
||||
Reticle.visible = true;
|
||||
return ControllerDispatcherUtils.makeRunningValues(true, [], []);
|
||||
} else {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
} else if (hmdChanged && !Reticle.visible) {
|
||||
Reticle.visible = true;
|
||||
}
|
||||
|
||||
return ControllerDispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function(controllerData, deltaTime) {
|
||||
var now = Date.now();
|
||||
var hmdActive = HMD.active;
|
||||
if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now) || !hmdActive) {
|
||||
if (!hmdActive) {
|
||||
Reticle.visible = true;
|
||||
} else {
|
||||
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);
|
||||
})();
|
|
@ -0,0 +1,226 @@
|
|||
"use strict";
|
||||
|
||||
// nearGrabEntity.js
|
||||
//
|
||||
// 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, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, enableDispatcherModule,
|
||||
disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE,
|
||||
makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGrabbableGroupParent, Vec3,
|
||||
cloneEntity, entityIsCloneable, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE,
|
||||
distanceBetweenPointAndEntityBoundingBox, getGrabbableData, getEnabledModuleByName, DISPATCHER_PROPERTIES, HMD,
|
||||
NEAR_GRAB_DISTANCE
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/cloneEntityUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() {
|
||||
|
||||
function NearGrabEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.targetEntityID = null;
|
||||
this.grabbing = false;
|
||||
this.cloneAllowed = true;
|
||||
this.grabID = null;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
500,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
this.startGrab = function (targetProps) {
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
}
|
||||
|
||||
var grabData = getGrabbableData(targetProps);
|
||||
|
||||
var handJointIndex;
|
||||
if (HMD.mounted && HMD.isHandControllerAvailable() && grabData.grabFollowsController) {
|
||||
handJointIndex = getControllerJointIndex(this.hand);
|
||||
} else {
|
||||
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
}
|
||||
|
||||
this.targetEntityID = targetProps.id;
|
||||
|
||||
var relativePosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex);
|
||||
var relativeRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex);
|
||||
this.grabID = MyAvatar.grab(targetProps.id, handJointIndex, relativePosition, relativeRotation);
|
||||
};
|
||||
|
||||
this.startNearGrabEntity = function (targetProps) {
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
|
||||
this.startGrab(targetProps);
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(targetProps.id, "startNearGrab", args);
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'grab',
|
||||
grabbedEntity: targetProps.id,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
|
||||
this.grabbing = true;
|
||||
};
|
||||
|
||||
this.endGrab = function () {
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
this.grabID = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.endNearGrabEntity = function () {
|
||||
this.endGrab();
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args);
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'release',
|
||||
grabbedEntity: this.targetEntityID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
|
||||
this.grabbing = false;
|
||||
this.targetEntityID = null;
|
||||
};
|
||||
|
||||
this.getTargetProps = function (controllerData) {
|
||||
// nearbyEntityProperties is already sorted by length from controller
|
||||
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
var nearGrabDistance = NEAR_GRAB_DISTANCE * sensorScaleFactor;
|
||||
var nearGrabRadius = NEAR_GRAB_RADIUS * sensorScaleFactor;
|
||||
for (var i = 0; i < nearbyEntityProperties.length; i++) {
|
||||
var props = nearbyEntityProperties[i];
|
||||
var grabPosition = controllerData.controllerLocations[this.hand].position; // Is offset from hand position.
|
||||
var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props);
|
||||
var distance = Vec3.distance(grabPosition, props.position);
|
||||
if ((dist > nearGrabDistance) ||
|
||||
(distance > nearGrabRadius)) { // Only smallish entities can be near grabbed.
|
||||
continue;
|
||||
}
|
||||
if (entityIsGrabbable(props) || entityIsCloneable(props)) {
|
||||
if (!entityIsCloneable(props)) {
|
||||
// if we've attempted to grab a non-cloneable child, roll up to the root of the tree
|
||||
var groupRootProps = findGrabbableGroupParent(controllerData, props);
|
||||
if (entityIsGrabbable(groupRootProps)) {
|
||||
return groupRootProps;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData, deltaTime) {
|
||||
this.targetEntityID = null;
|
||||
this.grabbing = false;
|
||||
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE &&
|
||||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
this.cloneAllowed = true;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var scaleModuleName = this.hand === RIGHT_HAND ? "RightScaleEntity" : "LeftScaleEntity";
|
||||
var scaleModule = getEnabledModuleByName(scaleModuleName);
|
||||
if (scaleModule && (scaleModule.grabbedThingID || scaleModule.isReady(controllerData).active)) {
|
||||
// we're rescaling -- don't start a grab.
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var targetProps = this.getTargetProps(controllerData);
|
||||
if (targetProps) {
|
||||
this.targetEntityID = targetProps.id;
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
} else {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
|
||||
if (this.grabbing) {
|
||||
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
|
||||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
this.endNearGrabEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||
if (!props) {
|
||||
props = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||
if (!props) {
|
||||
// entity was deleted
|
||||
this.grabbing = false;
|
||||
this.targetEntityID = null;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args);
|
||||
} else {
|
||||
// still searching
|
||||
var readiness = this.isReady(controllerData);
|
||||
if (!readiness.active) {
|
||||
return readiness;
|
||||
}
|
||||
if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||
// switch to grab
|
||||
var targetProps = this.getTargetProps(controllerData);
|
||||
var targetCloneable = entityIsCloneable(targetProps);
|
||||
|
||||
if (targetCloneable) {
|
||||
if (this.cloneAllowed) {
|
||||
var cloneID = cloneEntity(targetProps);
|
||||
if (cloneID !== null) {
|
||||
var cloneProps = Entities.getEntityProperties(cloneID, DISPATCHER_PROPERTIES);
|
||||
cloneProps.id = cloneID;
|
||||
this.grabbing = true;
|
||||
this.targetEntityID = cloneID;
|
||||
this.startNearGrabEntity(cloneProps);
|
||||
this.cloneAllowed = false; // prevent another clone call until inputs released
|
||||
}
|
||||
}
|
||||
} else if (targetProps) {
|
||||
this.grabbing = true;
|
||||
this.startNearGrabEntity(targetProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
if (this.targetEntityID) {
|
||||
this.endNearGrabEntity();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var leftNearGrabEntity = new NearGrabEntity(LEFT_HAND);
|
||||
var rightNearGrabEntity = new NearGrabEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftNearGrabEntity", leftNearGrabEntity);
|
||||
enableDispatcherModule("RightNearGrabEntity", rightNearGrabEntity);
|
||||
|
||||
function cleanup() {
|
||||
leftNearGrabEntity.cleanup();
|
||||
rightNearGrabEntity.cleanup();
|
||||
disableDispatcherModule("LeftNearGrabEntity");
|
||||
disableDispatcherModule("RightNearGrabEntity");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,91 @@
|
|||
"use strict";
|
||||
|
||||
// nearGrabHyperLinkEntity.js
|
||||
//
|
||||
// Created by Dante Ruiz on 03/02/2018
|
||||
//
|
||||
// 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, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
|
||||
makeDispatcherModuleParameters, makeRunningValues, TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, BUMPER_ON_VALUE, AddressManager
|
||||
*/
|
||||
|
||||
(function() {
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
function NearGrabHyperLinkEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.targetEntityID = null;
|
||||
this.hyperlink = "";
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
485,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
|
||||
this.getTargetProps = function(controllerData) {
|
||||
var nearbyEntitiesProperties = controllerData.nearbyEntityProperties[this.hand];
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
for (var i = 0; i < nearbyEntitiesProperties.length; i++) {
|
||||
var props = nearbyEntitiesProperties[i];
|
||||
if (props.distance > NEAR_GRAB_RADIUS * sensorScaleFactor) {
|
||||
continue;
|
||||
}
|
||||
if (props.href !== "" && props.href !== undefined) {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.isReady = function(controllerData) {
|
||||
this.targetEntityID = null;
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE &&
|
||||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var targetProps = this.getTargetProps(controllerData);
|
||||
if (targetProps) {
|
||||
this.hyperlink = targetProps.href;
|
||||
this.targetEntityID = targetProps.id;
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function(controllerData) {
|
||||
if ((controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
|
||||
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) || this.hyperlink === "") {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
if (controllerData.triggerClicks[this.hand] ||
|
||||
controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||
AddressManager.handleLookupString(this.hyperlink);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
return makeRunningValues(true, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftNearGrabHyperLinkEntity = new NearGrabHyperLinkEntity(LEFT_HAND);
|
||||
var rightNearGrabHyperLinkEntity = new NearGrabHyperLinkEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftNearGrabHyperLink", leftNearGrabHyperLinkEntity);
|
||||
enableDispatcherModule("RightNearGrabHyperLink", rightNearGrabHyperLinkEntity);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftNearGrabHyperLink");
|
||||
disableDispatcherModule("RightNearGrabHyperLink");
|
||||
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,255 @@
|
|||
"use strict";
|
||||
|
||||
// nearParentGrabOverlay.js
|
||||
//
|
||||
// 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, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex,
|
||||
enableDispatcherModule, disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
|
||||
makeDispatcherModuleParameters, Overlays, makeRunningValues, Vec3, resizeTablet, getTabletWidthFromSettings,
|
||||
NEAR_GRAB_RADIUS, HMD, Uuid, getEnabledModuleByName
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/utils.js");
|
||||
|
||||
(function() {
|
||||
|
||||
// XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
|
||||
// XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
|
||||
|
||||
function NearParentingGrabOverlay(hand) {
|
||||
this.hand = hand;
|
||||
this.grabbedThingID = null;
|
||||
this.previousParentID = {};
|
||||
this.previousParentJointIndex = {};
|
||||
this.previouslyUnhooked = {};
|
||||
this.robbed = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
90,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
|
||||
// XXX does handJointIndex change if the avatar changes?
|
||||
this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
|
||||
this.getOtherModule = function() {
|
||||
return (this.hand === RIGHT_HAND) ? leftNearParentingGrabOverlay : rightNearParentingGrabOverlay;
|
||||
};
|
||||
|
||||
this.otherHandIsParent = function(props) {
|
||||
return this.getOtherModule().thisHandIsParent(props);
|
||||
};
|
||||
|
||||
this.isGrabbedThingVisible = function() {
|
||||
return Overlays.getProperty(this.grabbedThingID, "visible");
|
||||
};
|
||||
|
||||
this.thisHandIsParent = function(props) {
|
||||
if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== MyAvatar.SELF_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
if (props.parentJointIndex === handJointIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var controllerJointIndex = this.controllerJointIndex;
|
||||
if (props.parentJointIndex === controllerJointIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
|
||||
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
|
||||
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
|
||||
|
||||
if (props.parentJointIndex === controllerCRJointIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.getGrabbedProperties = function() {
|
||||
return {
|
||||
position: Overlays.getProperty(this.grabbedThingID, "position"),
|
||||
rotation: Overlays.getProperty(this.grabbedThingID, "rotation"),
|
||||
parentID: Overlays.getProperty(this.grabbedThingID, "parentID"),
|
||||
parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"),
|
||||
dynamic: false,
|
||||
shapeType: "none"
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
this.startNearParentingGrabOverlay = function (controllerData) {
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
|
||||
this.controllerJointIndex = getControllerJointIndex(this.hand);
|
||||
var handJointIndex = this.controllerJointIndex;
|
||||
|
||||
var grabbedProperties = this.getGrabbedProperties();
|
||||
|
||||
var reparentProps = {
|
||||
parentID: MyAvatar.SELF_ID,
|
||||
parentJointIndex: handJointIndex,
|
||||
velocity: {x: 0, y: 0, z: 0},
|
||||
angularVelocity: {x: 0, y: 0, z: 0}
|
||||
};
|
||||
|
||||
if (this.thisHandIsParent(grabbedProperties)) {
|
||||
// this should never happen, but if it does, don't set previous parent to be this hand.
|
||||
// this.previousParentID[this.grabbedThingID] = NULL;
|
||||
// this.previousParentJointIndex[this.grabbedThingID] = -1;
|
||||
} else if (this.otherHandIsParent(grabbedProperties)) {
|
||||
// the other hand is parent. Steal the object and information
|
||||
var otherModule = this.getOtherModule();
|
||||
this.previousParentID[this.grabbedThingID] = otherModule.previousParentID[this.grabbedThingID];
|
||||
this.previousParentJointIndex[this.grabbedThingID] = otherModule.previousParentJointIndex[this.grabbedThingID];
|
||||
otherModule.robbed = true;
|
||||
} else {
|
||||
this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID;
|
||||
this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
|
||||
}
|
||||
|
||||
// resizeTablet to counter adjust offsets to account for change of scale from sensorToWorldMatrix
|
||||
if (HMD.tabletID && this.grabbedThingID === HMD.tabletID) {
|
||||
reparentAndScaleTablet(getTabletWidthFromSettings(), reparentProps);
|
||||
} else {
|
||||
Entities.editEntity(this.grabbedThingID, reparentProps);
|
||||
}
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'grab',
|
||||
grabbedEntity: this.grabbedThingID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
};
|
||||
|
||||
this.endNearParentingGrabOverlay = function () {
|
||||
var previousParentID = this.previousParentID[this.grabbedThingID];
|
||||
if ((previousParentID === Uuid.NULL || previousParentID === null) && !this.robbed) {
|
||||
Overlays.editOverlay(this.grabbedThingID, {
|
||||
parentID: Uuid.NULL,
|
||||
parentJointIndex: -1
|
||||
});
|
||||
} else if (!this.robbed){
|
||||
// before we grabbed it, overlay was a child of something; put it back.
|
||||
Entities.editEntity(this.grabbedThingID, {
|
||||
parentID: this.previousParentID[this.grabbedThingID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID]
|
||||
});
|
||||
|
||||
// resizeTablet to counter adjust offsets to account for change of scale from sensorToWorldMatrix
|
||||
if (HMD.tabletID && this.grabbedThingID === HMD.tabletID) {
|
||||
resizeTablet(getTabletWidthFromSettings(), this.previousParentJointIndex[this.grabbedThingID]);
|
||||
}
|
||||
}
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'release',
|
||||
grabbedEntity: this.grabbedThingID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
|
||||
this.grabbedThingID = null;
|
||||
};
|
||||
|
||||
this.getTargetID = function(overlays, controllerData) {
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
for (var i = 0; i < overlays.length; i++) {
|
||||
var overlayPosition = Overlays.getProperty(overlays[i], "position");
|
||||
var handPosition = controllerData.controllerLocations[this.hand].position;
|
||||
var distance = Vec3.distance(overlayPosition, handPosition);
|
||||
if (distance <= NEAR_GRAB_RADIUS * sensorScaleFactor) {
|
||||
if (overlays[i] !== HMD.miniTabletID || controllerData.secondaryValues[this.hand] === 0) {
|
||||
// Don't grab mini tablet with grip.
|
||||
return overlays[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.isEditing = function () {
|
||||
var inEditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightHandInEditMode" : "LeftHandInEditMode");
|
||||
if (inEditModeModule && inEditModeModule.isEditing) {
|
||||
return true;
|
||||
}
|
||||
var inVREditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightHandInVREditMode" : "LeftHandInVREditMode");
|
||||
if (inVREditModeModule && inVREditModeModule.isEditing) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)
|
||||
|| this.isEditing()) {
|
||||
this.robbed = false;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
this.grabbedThingID = null;
|
||||
|
||||
var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand];
|
||||
var grabbableOverlays = candidateOverlays.filter(function(overlayID) {
|
||||
return Overlays.getProperty(overlayID, "grabbable");
|
||||
});
|
||||
|
||||
var targetID = this.getTargetID(grabbableOverlays, controllerData);
|
||||
if (targetID && !this.robbed) {
|
||||
this.grabbedThingID = targetID;
|
||||
this.startNearParentingGrabOverlay(controllerData);
|
||||
return makeRunningValues(true, [this.grabbedThingID], []);
|
||||
} else {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)
|
||||
|| this.isEditing() || !this.isGrabbedThingVisible()) {
|
||||
this.endNearParentingGrabOverlay();
|
||||
this.robbed = false;
|
||||
return makeRunningValues(false, [], []);
|
||||
} else {
|
||||
// check if someone stole the target from us
|
||||
var grabbedProperties = this.getGrabbedProperties();
|
||||
if (!this.thisHandIsParent(grabbedProperties)) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
return makeRunningValues(true, [this.grabbedThingID], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
if (this.grabbedThingID) {
|
||||
this.endNearParentingGrabOverlay();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var leftNearParentingGrabOverlay = new NearParentingGrabOverlay(LEFT_HAND);
|
||||
var rightNearParentingGrabOverlay = new NearParentingGrabOverlay(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftNearParentingGrabOverlay", leftNearParentingGrabOverlay);
|
||||
enableDispatcherModule("RightNearParentingGrabOverlay", rightNearParentingGrabOverlay);
|
||||
|
||||
function cleanup() {
|
||||
leftNearParentingGrabOverlay.cleanup();
|
||||
rightNearParentingGrabOverlay.cleanup();
|
||||
disableDispatcherModule("LeftNearParentingGrabOverlay");
|
||||
disableDispatcherModule("RightNearParentingGrabOverlay");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// nearTabletHighlight.js
|
||||
//
|
||||
// Highlight the tablet if a hand is near enough to grab it and it isn't grabbed.
|
||||
//
|
||||
// Created by David Rowe on 28 Aug 2018.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* global LEFT_HAND, RIGHT_HAND, makeDispatcherModuleParameters, makeRunningValues, enableDispatcherModule,
|
||||
* disableDispatcherModule, getEnabledModuleByName */
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
(function () {
|
||||
|
||||
"use strict";
|
||||
|
||||
var TABLET_GRABBABLE_SELECTION_NAME = "tabletGrabbableSelection";
|
||||
var TABLET_GRABBABLE_SELECTION_STYLE = {
|
||||
outlineUnoccludedColor: { red: 0, green: 180, blue: 239 }, // #00b4ef
|
||||
outlineUnoccludedAlpha: 1,
|
||||
outlineOccludedColor: { red: 0, green: 0, blue: 0 },
|
||||
outlineOccludedAlpha: 0,
|
||||
fillUnoccludedColor: { red: 0, green: 0, blue: 0 },
|
||||
fillUnoccludedAlpha: 0,
|
||||
fillOccludedColor: { red: 0, green: 0, blue: 0 },
|
||||
fillOccludedAlpha: 0,
|
||||
outlineWidth: 4,
|
||||
isOutlineSmooth: false
|
||||
};
|
||||
|
||||
var isTabletNearGrabbable = [false, false];
|
||||
var isTabletHighlighted = false;
|
||||
|
||||
function setTabletNearGrabbable(hand, enabled) {
|
||||
if (enabled === isTabletNearGrabbable[hand]) {
|
||||
return;
|
||||
}
|
||||
|
||||
isTabletNearGrabbable[hand] = enabled;
|
||||
|
||||
if (isTabletNearGrabbable[LEFT_HAND] || isTabletNearGrabbable[RIGHT_HAND]) {
|
||||
if (!isTabletHighlighted) {
|
||||
Selection.addToSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID);
|
||||
isTabletHighlighted = true;
|
||||
}
|
||||
} else {
|
||||
if (isTabletHighlighted) {
|
||||
Selection.removeFromSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID);
|
||||
isTabletHighlighted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function NearTabletHighlight(hand) {
|
||||
this.hand = hand;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
95,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100
|
||||
);
|
||||
|
||||
this.isEditing = function () {
|
||||
var inEditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightHandInEditMode" : "LeftHandInEditMode");
|
||||
if (inEditModeModule && inEditModeModule.isEditing) {
|
||||
return true;
|
||||
}
|
||||
var inVREditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightHandInVREditMode" : "LeftHandInVREditMode");
|
||||
if (inVREditModeModule && inVREditModeModule.isEditing) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.isNearTablet = function (controllerData) {
|
||||
return HMD.tabletID && controllerData.nearbyOverlayIDs[this.hand].indexOf(HMD.tabletID) !== -1;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
if (!this.isEditing() && this.isNearTablet(controllerData)) {
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
setTabletNearGrabbable(this.hand, false);
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
if (this.isEditing() || !this.isNearTablet(controllerData)) {
|
||||
setTabletNearGrabbable(this.hand, false);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand]) {
|
||||
setTabletNearGrabbable(this.hand, false);
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
setTabletNearGrabbable(this.hand, true);
|
||||
return makeRunningValues(true, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftNearTabletHighlight = new NearTabletHighlight(LEFT_HAND);
|
||||
var rightNearTabletHighlight = new NearTabletHighlight(RIGHT_HAND);
|
||||
enableDispatcherModule("LeftNearTabletHighlight", leftNearTabletHighlight);
|
||||
enableDispatcherModule("RightNearTabletHighlight", rightNearTabletHighlight);
|
||||
|
||||
function onDisplayModeChanged() {
|
||||
if (HMD.active) {
|
||||
Selection.enableListHighlight(TABLET_GRABBABLE_SELECTION_NAME, TABLET_GRABBABLE_SELECTION_STYLE);
|
||||
} else {
|
||||
Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME);
|
||||
Selection.clearSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME);
|
||||
}
|
||||
}
|
||||
HMD.displayModeChanged.connect(onDisplayModeChanged);
|
||||
HMD.mountedChanged.connect(onDisplayModeChanged);
|
||||
onDisplayModeChanged();
|
||||
|
||||
function cleanUp() {
|
||||
disableDispatcherModule("LeftNearTabletHighlight");
|
||||
disableDispatcherModule("RightNearTabletHighlight");
|
||||
Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME);
|
||||
}
|
||||
Script.scriptEnding.connect(cleanUp);
|
||||
|
||||
}());
|
|
@ -0,0 +1,120 @@
|
|||
"use strict";
|
||||
|
||||
// nearTrigger.js
|
||||
//
|
||||
// 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, Entities, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, getGrabbableData,
|
||||
Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
(function() {
|
||||
|
||||
function entityWantsNearTrigger(props) {
|
||||
var grabbableData = getGrabbableData(props);
|
||||
return grabbableData.triggerable;
|
||||
}
|
||||
|
||||
function NearTriggerEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.targetEntityID = null;
|
||||
this.grabbing = false;
|
||||
this.previousParentID = {};
|
||||
this.previousParentJointIndex = {};
|
||||
this.previouslyUnhooked = {};
|
||||
this.startSent = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
480,
|
||||
this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
this.getTargetProps = function (controllerData) {
|
||||
// nearbyEntityProperties is already sorted by length from controller
|
||||
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
for (var i = 0; i < nearbyEntityProperties.length; i++) {
|
||||
var props = nearbyEntityProperties[i];
|
||||
var handPosition = controllerData.controllerLocations[this.hand].position;
|
||||
var distance = Vec3.distance(props.position, handPosition);
|
||||
if (distance > NEAR_GRAB_RADIUS * sensorScaleFactor) {
|
||||
continue;
|
||||
}
|
||||
if (entityWantsNearTrigger(props)) {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.startNearTrigger = function (controllerData) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "startNearTrigger", args);
|
||||
};
|
||||
|
||||
this.continueNearTrigger = function (controllerData) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "continueNearTrigger", args);
|
||||
};
|
||||
|
||||
this.endNearTrigger = function (controllerData) {
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "stopNearTrigger", args);
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
this.targetEntityID = null;
|
||||
|
||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var targetProps = this.getTargetProps(controllerData);
|
||||
if (targetProps) {
|
||||
this.targetEntityID = targetProps.id;
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
} else {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.run = function (controllerData) {
|
||||
if (!this.startSent) {
|
||||
this.startNearTrigger(controllerData);
|
||||
this.startSent = true;
|
||||
} else if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||
this.endNearTrigger(controllerData);
|
||||
this.startSent = false;
|
||||
return makeRunningValues(false, [], []);
|
||||
} else {
|
||||
this.continueNearTrigger(controllerData);
|
||||
}
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
if (this.targetEntityID) {
|
||||
this.endNearTrigger();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var leftNearTriggerEntity = new NearTriggerEntity(LEFT_HAND);
|
||||
var rightNearTriggerEntity = new NearTriggerEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftNearTriggerEntity", leftNearTriggerEntity);
|
||||
enableDispatcherModule("RightNearTriggerEntity", rightNearTriggerEntity);
|
||||
|
||||
function cleanup() {
|
||||
leftNearTriggerEntity.cleanup();
|
||||
rightNearTriggerEntity.cleanup();
|
||||
disableDispatcherModule("LeftNearTriggerEntity");
|
||||
disableDispatcherModule("RightNearTriggerEntity");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
|
@ -0,0 +1,64 @@
|
|||
"use strict";
|
||||
|
||||
// Created by Jason C. Najera on 3/7/2019
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Handles Push-to-Talk functionality for HMD mode.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
function PushToTalkHandler() {
|
||||
var _this = this;
|
||||
this.active = false;
|
||||
|
||||
this.shouldTalk = function (controllerData) {
|
||||
// Set up test against controllerData here...
|
||||
var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND];
|
||||
return (gripVal) ? true : false;
|
||||
};
|
||||
|
||||
this.shouldStopTalking = function (controllerData) {
|
||||
var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND];
|
||||
return (gripVal) ? false : true;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData, deltaTime) {
|
||||
if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) {
|
||||
Audio.pushingToTalk = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) {
|
||||
Audio.pushingToTalk = false;
|
||||
print("Stop pushing to talk.");
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
return makeRunningValues(true, [], []);
|
||||
};
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
950,
|
||||
["head"],
|
||||
[],
|
||||
100);
|
||||
}
|
||||
|
||||
var pushToTalk = new PushToTalkHandler();
|
||||
enableDispatcherModule("PushToTalk", pushToTalk);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("PushToTalk");
|
||||
};
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -0,0 +1,88 @@
|
|||
// scaleAvatar.js
|
||||
//
|
||||
// Created by Dante Ruiz on 9/11/17
|
||||
//
|
||||
// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects.
|
||||
//
|
||||
// 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, Vec3, MyAvatar, RIGHT_HAND */
|
||||
|
||||
(function () {
|
||||
var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
function clamp(val, min, max) {
|
||||
return Math.max(min, Math.min(max, val));
|
||||
}
|
||||
|
||||
function ScaleAvatar(hand) {
|
||||
this.hand = hand;
|
||||
this.scalingStartAvatarScale = 0;
|
||||
this.scalingStartDistance = 0;
|
||||
|
||||
this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
|
||||
120,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100
|
||||
);
|
||||
|
||||
this.otherHand = function() {
|
||||
return this.hand === dispatcherUtils.RIGHT_HAND ? dispatcherUtils.LEFT_HAND : dispatcherUtils.RIGHT_HAND;
|
||||
};
|
||||
|
||||
this.getOtherModule = function() {
|
||||
var otherModule = this.hand === dispatcherUtils.RIGHT_HAND ? leftScaleAvatar : rightScaleAvatar;
|
||||
return otherModule;
|
||||
};
|
||||
|
||||
this.triggersPressed = function(controllerData) {
|
||||
if (controllerData.triggerClicks[this.hand] &&
|
||||
controllerData.secondaryValues[this.hand] > dispatcherUtils.BUMPER_ON_VALUE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.isReady = function(controllerData) {
|
||||
var otherModule = this.getOtherModule();
|
||||
if (this.triggersPressed(controllerData) && otherModule.triggersPressed(controllerData)) {
|
||||
this.scalingStartAvatarScale = MyAvatar.scale;
|
||||
this.scalingStartDistance = Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position,
|
||||
controllerData.controllerLocations[this.otherHand()].position));
|
||||
return dispatcherUtils.makeRunningValues(true, [], []);
|
||||
}
|
||||
return dispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function(controllerData) {
|
||||
var otherModule = this.getOtherModule();
|
||||
if (this.triggersPressed(controllerData) && otherModule.triggersPressed(controllerData)) {
|
||||
if (this.hand === dispatcherUtils.RIGHT_HAND) {
|
||||
var scalingCurrentDistance =
|
||||
Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position,
|
||||
controllerData.controllerLocations[this.otherHand()].position));
|
||||
|
||||
var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale;
|
||||
MyAvatar.scale = clamp(newAvatarScale, MyAvatar.getDomainMinScale(), MyAvatar.getDomainMaxScale());
|
||||
MyAvatar.scaleChanged();
|
||||
}
|
||||
return dispatcherUtils.makeRunningValues(true, [], []);
|
||||
}
|
||||
return dispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftScaleAvatar = new ScaleAvatar(dispatcherUtils.LEFT_HAND);
|
||||
var rightScaleAvatar = new ScaleAvatar(dispatcherUtils.RIGHT_HAND);
|
||||
|
||||
dispatcherUtils.enableDispatcherModule("LeftScaleAvatar", leftScaleAvatar);
|
||||
dispatcherUtils.enableDispatcherModule("RightScaleAvatar", rightScaleAvatar);
|
||||
|
||||
function cleanup() {
|
||||
dispatcherUtils.disableDispatcherModule("LeftScaleAvatar");
|
||||
dispatcherUtils.disableDispatcherModule("RightScaleAvatar");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
})();
|
|
@ -0,0 +1,110 @@
|
|||
// scaleEntity.js
|
||||
//
|
||||
// Created by Dante Ruiz on 9/18/17
|
||||
//
|
||||
// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects.
|
||||
//
|
||||
// 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, Vec3, MyAvatar, Entities, RIGHT_HAND, entityIsGrabbable */
|
||||
|
||||
(function() {
|
||||
var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
function ScaleEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.grabbedThingID = false;
|
||||
this.scalingStartDistance = false;
|
||||
this.scalingStartDimensions = false;
|
||||
|
||||
this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
|
||||
120,
|
||||
this.hand === RIGHT_HAND ? ["rightHandTrigger"] : ["leftHandTrigger"],
|
||||
[],
|
||||
100
|
||||
);
|
||||
|
||||
this.otherHand = function() {
|
||||
return this.hand === dispatcherUtils.RIGHT_HAND ? dispatcherUtils.LEFT_HAND : dispatcherUtils.RIGHT_HAND;
|
||||
};
|
||||
|
||||
this.otherModule = function() {
|
||||
return this.hand === dispatcherUtils.RIGHT_HAND ? leftScaleEntity : rightScaleEntity;
|
||||
};
|
||||
|
||||
this.bumperPressed = function(controllerData) {
|
||||
return ( controllerData.secondaryValues[this.hand] > dispatcherUtils.BUMPER_ON_VALUE);
|
||||
};
|
||||
|
||||
this.getTargetProps = function(controllerData) {
|
||||
// nearbyEntityProperties is already sorted by length from controller
|
||||
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
for (var i = 0; i < nearbyEntityProperties.length; i++) {
|
||||
var props = nearbyEntityProperties[i];
|
||||
var handPosition = controllerData.controllerLocations[this.hand].position;
|
||||
var distance = Vec3.distance(props.position, handPosition);
|
||||
if (distance > dispatcherUtils.NEAR_GRAB_RADIUS * sensorScaleFactor) {
|
||||
continue;
|
||||
}
|
||||
if ((dispatcherUtils.entityIsGrabbable(props) ||
|
||||
dispatcherUtils.propsArePhysical(props)) && !props.locked) {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.isReady = function(controllerData) {
|
||||
var otherModule = this.otherModule();
|
||||
if (this.bumperPressed(controllerData) && otherModule.bumperPressed(controllerData)) {
|
||||
var thisHandTargetProps = this.getTargetProps(controllerData);
|
||||
var otherHandTargetProps = otherModule.getTargetProps(controllerData);
|
||||
if (thisHandTargetProps && otherHandTargetProps) {
|
||||
if (thisHandTargetProps.id === otherHandTargetProps.id) {
|
||||
if (!entityIsGrabbable(thisHandTargetProps)) {
|
||||
return dispatcherUtils.makeRunningValues(false, [], []);
|
||||
}
|
||||
this.grabbedThingID = thisHandTargetProps.id;
|
||||
this.scalingStartDistance =
|
||||
Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position,
|
||||
controllerData.controllerLocations[this.otherHand()].position));
|
||||
this.scalingStartDimensions = thisHandTargetProps.dimensions;
|
||||
return dispatcherUtils.makeRunningValues(true, [], []);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.grabbedThingID = false;
|
||||
return dispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function(controllerData) {
|
||||
var otherModule = this.otherModule();
|
||||
if (this.bumperPressed(controllerData) && otherModule.bumperPressed(controllerData)) {
|
||||
if (this.hand === dispatcherUtils.RIGHT_HAND) {
|
||||
var scalingCurrentDistance =
|
||||
Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position,
|
||||
controllerData.controllerLocations[this.otherHand()].position));
|
||||
var currentRescale = scalingCurrentDistance / this.scalingStartDistance;
|
||||
var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions);
|
||||
Entities.editEntity(this.grabbedThingID, { localDimensions: newDimensions });
|
||||
}
|
||||
return dispatcherUtils.makeRunningValues(true, [], []);
|
||||
}
|
||||
this.grabbedThingID = false;
|
||||
return dispatcherUtils.makeRunningValues(false, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftScaleEntity = new ScaleEntity(dispatcherUtils.LEFT_HAND);
|
||||
var rightScaleEntity = new ScaleEntity(dispatcherUtils.RIGHT_HAND);
|
||||
|
||||
dispatcherUtils.enableDispatcherModule("LeftScaleEntity", leftScaleEntity);
|
||||
dispatcherUtils.enableDispatcherModule("RightScaleEntity", rightScaleEntity);
|
||||
|
||||
function cleanup() {
|
||||
dispatcherUtils.disableDispatcherModule("LeftScaleEntity");
|
||||
dispatcherUtils.disableDispatcherModule("RightScaleEntity");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
})();
|
|
@ -0,0 +1,220 @@
|
|||
"use strict";
|
||||
|
||||
// stylusInput.js
|
||||
//
|
||||
// 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, MyAvatar, Controller, Uuid, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
|
||||
makeRunningValues, Vec3, makeDispatcherModuleParameters, Overlays, HMD, Settings, getEnabledModuleByName, Pointers,
|
||||
Picks, PickType
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() {
|
||||
function isNearStylusTarget(stylusTargets, maxNormalDistance) {
|
||||
var stylusTargetIDs = [];
|
||||
for (var index = 0; index < stylusTargets.length; index++) {
|
||||
var stylusTarget = stylusTargets[index];
|
||||
if (stylusTarget.distance <= maxNormalDistance && !(HMD.tabletID && stylusTarget.id === HMD.tabletID)) {
|
||||
stylusTargetIDs.push(stylusTarget.id);
|
||||
}
|
||||
}
|
||||
return stylusTargetIDs;
|
||||
}
|
||||
|
||||
function getOverlayDistance(controllerPosition, overlayID) {
|
||||
var position = Overlays.getProperty(overlayID, "position");
|
||||
return {
|
||||
id: overlayID,
|
||||
distance: Vec3.distance(position, controllerPosition)
|
||||
};
|
||||
}
|
||||
|
||||
function StylusInput(hand) {
|
||||
this.hand = hand;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
100,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
this.pointer = Pointers.createPointer(PickType.Stylus, {
|
||||
hand: this.hand,
|
||||
filter: Picks.PICK_OVERLAYS,
|
||||
hover: true,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
this.disable = false;
|
||||
|
||||
this.otherModuleNeedsToRun = function(controllerData) {
|
||||
var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
|
||||
var grabOverlayModule = getEnabledModuleByName(grabOverlayModuleName);
|
||||
var grabEntityModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity";
|
||||
var grabEntityModule = getEnabledModuleByName(grabEntityModuleName);
|
||||
var grabOverlayModuleReady = grabOverlayModule ? grabOverlayModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
var grabEntityModuleReady = grabEntityModule ? grabEntityModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
var farGrabModuleName = this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity";
|
||||
var farGrabModule = getEnabledModuleByName(farGrabModuleName);
|
||||
var farGrabModuleReady = farGrabModule ? farGrabModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
var nearTabletHighlightModuleName =
|
||||
this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight";
|
||||
var nearTabletHighlightModule = getEnabledModuleByName(nearTabletHighlightModuleName);
|
||||
var nearTabletHighlightModuleReady = nearTabletHighlightModule
|
||||
? nearTabletHighlightModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||
return grabOverlayModuleReady.active || farGrabModuleReady.active || grabEntityModuleReady.active
|
||||
|| nearTabletHighlightModuleReady.active;
|
||||
};
|
||||
|
||||
this.overlayLaserActive = function(controllerData) {
|
||||
var rightOverlayLaserModule = getEnabledModuleByName("RightWebSurfaceLaserInput");
|
||||
var leftOverlayLaserModule = getEnabledModuleByName("LeftWebSurfaceLaserInput");
|
||||
var rightModuleRunning = rightOverlayLaserModule ? rightOverlayLaserModule.isReady(controllerData).active : false;
|
||||
var leftModuleRunning = leftOverlayLaserModule ? leftOverlayLaserModule.isReady(controllerData).active : false;
|
||||
return leftModuleRunning || rightModuleRunning;
|
||||
};
|
||||
|
||||
this.processStylus = function(controllerData) {
|
||||
if (this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) {
|
||||
Pointers.setRenderState(this.pointer, "disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||
|
||||
// build list of stylus targets, near the stylusTip
|
||||
var stylusTargets = [];
|
||||
var candidateOverlays = controllerData.nearbyOverlayIDs;
|
||||
var controllerPosition = controllerData.controllerLocations[this.hand].position;
|
||||
var i, stylusTarget;
|
||||
|
||||
for (i = 0; i < candidateOverlays.length; i++) {
|
||||
if (!(HMD.tabletID && candidateOverlays[i] === HMD.tabletID) &&
|
||||
Overlays.getProperty(candidateOverlays[i], "visible")) {
|
||||
stylusTarget = getOverlayDistance(controllerPosition, candidateOverlays[i]);
|
||||
if (stylusTarget) {
|
||||
stylusTargets.push(stylusTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the tabletScreen, if it is valid
|
||||
if (HMD.tabletScreenID && HMD.tabletScreenID !== Uuid.NULL &&
|
||||
Overlays.getProperty(HMD.tabletScreenID, "visible")) {
|
||||
stylusTarget = getOverlayDistance(controllerPosition, HMD.tabletScreenID);
|
||||
if (stylusTarget) {
|
||||
stylusTargets.push(stylusTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// add the tablet home button.
|
||||
if (HMD.homeButtonID && HMD.homeButtonID !== Uuid.NULL &&
|
||||
Overlays.getProperty(HMD.homeButtonID, "visible")) {
|
||||
stylusTarget = getOverlayDistance(controllerPosition, HMD.homeButtonID);
|
||||
if (stylusTarget) {
|
||||
stylusTargets.push(stylusTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the mini tablet.
|
||||
if (HMD.miniTabletScreenID && Overlays.getProperty(HMD.miniTabletScreenID, "visible")) {
|
||||
stylusTarget = getOverlayDistance(controllerPosition, HMD.miniTabletScreenID);
|
||||
if (stylusTarget) {
|
||||
stylusTargets.push(stylusTarget);
|
||||
}
|
||||
}
|
||||
|
||||
const WEB_DISPLAY_STYLUS_DISTANCE = (Keyboard.raised && Keyboard.preferMalletsOverLasers) ? 0.2 : 0.5;
|
||||
var nearStylusTarget = isNearStylusTarget(stylusTargets, WEB_DISPLAY_STYLUS_DISTANCE * sensorScaleFactor);
|
||||
|
||||
if (nearStylusTarget.length !== 0) {
|
||||
if (!this.disable) {
|
||||
Pointers.setRenderState(this.pointer,"events on");
|
||||
Pointers.setIncludeItems(this.pointer, nearStylusTarget);
|
||||
} else {
|
||||
Pointers.setRenderState(this.pointer,"events off");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Pointers.setRenderState(this.pointer, "disabled");
|
||||
Pointers.setIncludeItems(this.pointer, []);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser";
|
||||
var isUsingStylus = Settings.getValue(PREFER_STYLUS_OVER_LASER, false);
|
||||
|
||||
if (isUsingStylus && this.processStylus(controllerData)) {
|
||||
Pointers.enablePointer(this.pointer);
|
||||
this.hand === RIGHT_HAND ? Keyboard.disableRightMallet() : Keyboard.disableLeftMallet();
|
||||
return makeRunningValues(true, [], []);
|
||||
} else {
|
||||
Pointers.disablePointer(this.pointer);
|
||||
if (Keyboard.raised && Keyboard.preferMalletsOverLasers) {
|
||||
this.hand === RIGHT_HAND ? Keyboard.enableRightMallet() : Keyboard.enableLeftMallet();
|
||||
}
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
return this.isReady(controllerData);
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
Pointers.removePointer(this.pointer);
|
||||
};
|
||||
}
|
||||
|
||||
function mouseHoverEnter(overlayID, event) {
|
||||
if (event.id === leftTabletStylusInput.pointer && !rightTabletStylusInput.disable && !leftTabletStylusInput.disable) {
|
||||
rightTabletStylusInput.disable = true;
|
||||
} else if (event.id === rightTabletStylusInput.pointer && !leftTabletStylusInput.disable && !rightTabletStylusInput.disable) {
|
||||
leftTabletStylusInput.disable = true;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseHoverLeave(overlayID, event) {
|
||||
if (event.id === leftTabletStylusInput.pointer) {
|
||||
rightTabletStylusInput.disable = false;
|
||||
} else if (event.id === rightTabletStylusInput.pointer) {
|
||||
leftTabletStylusInput.disable = false;
|
||||
}
|
||||
}
|
||||
|
||||
var HAPTIC_STYLUS_STRENGTH = 1.0;
|
||||
var HAPTIC_STYLUS_DURATION = 20.0;
|
||||
function mousePress(overlayID, event) {
|
||||
if (HMD.active) {
|
||||
if (event.id === leftTabletStylusInput.pointer && event.button === "Primary") {
|
||||
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, LEFT_HAND);
|
||||
} else if (event.id === rightTabletStylusInput.pointer && event.button === "Primary") {
|
||||
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, RIGHT_HAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var leftTabletStylusInput = new StylusInput(LEFT_HAND);
|
||||
var rightTabletStylusInput = new StylusInput(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftTabletStylusInput", leftTabletStylusInput);
|
||||
enableDispatcherModule("RightTabletStylusInput", rightTabletStylusInput);
|
||||
|
||||
Overlays.hoverEnterOverlay.connect(mouseHoverEnter);
|
||||
Overlays.hoverLeaveOverlay.connect(mouseHoverLeave);
|
||||
Overlays.mousePressOnOverlay.connect(mousePress);
|
||||
|
||||
this.cleanup = function () {
|
||||
leftTabletStylusInput.cleanup();
|
||||
rightTabletStylusInput.cleanup();
|
||||
disableDispatcherModule("LeftTabletStylusInput");
|
||||
disableDispatcherModule("RightTabletStylusInput");
|
||||
};
|
||||
Script.scriptEnding.connect(this.cleanup);
|
||||
}());
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,288 @@
|
|||
"use strict";
|
||||
|
||||
// webSurfaceLaserInput.js
|
||||
//
|
||||
// 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, Entities, enableDispatcherModule, disableDispatcherModule, makeRunningValues,
|
||||
makeDispatcherModuleParameters, Overlays, HMD, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, getEnabledModuleByName,
|
||||
ContextOverlay, Picks, makeLaserParams, Settings, MyAvatar, RIGHT_HAND, LEFT_HAND, DISPATCHER_PROPERTIES
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
(function() {
|
||||
const intersectionType = {
|
||||
None: 0,
|
||||
WebOverlay: 1,
|
||||
WebEntity: 2,
|
||||
HifiKeyboard: 3,
|
||||
Overlay: 4,
|
||||
HifiTablet: 5,
|
||||
};
|
||||
|
||||
function WebSurfaceLaserInput(hand) {
|
||||
this.hand = hand;
|
||||
this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND;
|
||||
this.running = false;
|
||||
this.ignoredObjects = [];
|
||||
this.intersectedType = intersectionType["None"];
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
160,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(hand, true));
|
||||
|
||||
this.getFarGrab = function () {
|
||||
return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("RightFarGrabEntity") : ("LeftFarGrabEntity"));
|
||||
};
|
||||
|
||||
this.farGrabActive = function () {
|
||||
var farGrab = this.getFarGrab();
|
||||
// farGrab will be null if module isn't loaded.
|
||||
if (farGrab) {
|
||||
return farGrab.targetIsNull();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.grabModuleWantsNearbyOverlay = function(controllerData) {
|
||||
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||
var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
|
||||
var nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||
if (nearGrabModule) {
|
||||
var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand];
|
||||
var grabbableOverlays = candidateOverlays.filter(function(overlayID) {
|
||||
return Overlays.getProperty(overlayID, "grabbable");
|
||||
});
|
||||
var target = nearGrabModule.getTargetID(grabbableOverlays, controllerData);
|
||||
if (target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity";
|
||||
nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||
if (nearGrabModule && nearGrabModule.isReady(controllerData)) {
|
||||
// check for if near parent module is active.
|
||||
var isNearGrabModuleActive = nearGrabModule.isReady(controllerData).active;
|
||||
if (isNearGrabModuleActive) {
|
||||
// if true, return true.
|
||||
return isNearGrabModuleActive;
|
||||
} else {
|
||||
// check near action grab entity as a second pass.
|
||||
nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity";
|
||||
nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||
if (nearGrabModule && nearGrabModule.isReady(controllerData)) {
|
||||
return nearGrabModule.isReady(controllerData).active;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nearTabletHighlightModule = getEnabledModuleByName(this.hand === RIGHT_HAND
|
||||
? "RightNearTabletHighlight" : "LeftNearTabletHighlight");
|
||||
if (nearTabletHighlightModule) {
|
||||
return nearTabletHighlightModule.isNearTablet(controllerData);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.getOtherModule = function() {
|
||||
return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput;
|
||||
};
|
||||
|
||||
this.addObjectToIgnoreList = function(controllerData) {
|
||||
if (Window.interstitialModeEnabled && !Window.isPhysicsEnabled()) {
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
var objectID = intersection.objectID;
|
||||
|
||||
if (intersection.type === Picks.INTERSECTED_OVERLAY) {
|
||||
var overlayIndex = this.ignoredObjects.indexOf(objectID);
|
||||
|
||||
var overlayName = Overlays.getProperty(objectID, "name");
|
||||
if (overlayName !== "Loading-Destination-Card-Text" && overlayName !== "Loading-Destination-Card-GoTo-Image" &&
|
||||
overlayName !== "Loading-Destination-Card-GoTo-Image-Hover") {
|
||||
var data = {
|
||||
action: 'add',
|
||||
id: objectID
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data));
|
||||
this.ignoredObjects.push(objectID);
|
||||
}
|
||||
} else if (intersection.type === Picks.INTERSECTED_ENTITY) {
|
||||
var entityIndex = this.ignoredObjects.indexOf(objectID);
|
||||
var data = {
|
||||
action: 'add',
|
||||
id: objectID
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data));
|
||||
this.ignoredObjects.push(objectID);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.restoreIgnoredObjects = function() {
|
||||
for (var index = 0; index < this.ignoredObjects.length; index++) {
|
||||
var data = {
|
||||
action: 'remove',
|
||||
id: this.ignoredObjects[index]
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data));
|
||||
}
|
||||
|
||||
this.ignoredObjects = [];
|
||||
};
|
||||
|
||||
this.getInteractableType = function(controllerData, triggerPressed, checkEntitiesOnly) {
|
||||
// allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger,
|
||||
// but for pointing at locked web entities or non-web overlays user must be pressing trigger
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
var objectID = intersection.objectID;
|
||||
if (intersection.type === Picks.INTERSECTED_OVERLAY && !checkEntitiesOnly) {
|
||||
if ((HMD.tabletID && objectID === HMD.tabletID) ||
|
||||
(HMD.tabletScreenID && objectID === HMD.tabletScreenID) ||
|
||||
(HMD.homeButtonID && objectID === HMD.homeButtonID)) {
|
||||
return intersectionType["HifiTablet"];
|
||||
} else {
|
||||
var overlayType = Overlays.getOverlayType(objectID);
|
||||
var type = intersectionType["None"];
|
||||
if (Keyboard.containsID(objectID) && !Keyboard.preferMalletsOverLasers) {
|
||||
type = intersectionType["HifiKeyboard"];
|
||||
} else if (overlayType === "web3d") {
|
||||
type = intersectionType["WebOverlay"];
|
||||
} else if (triggerPressed) {
|
||||
type = intersectionType["Overlay"];
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
} else if (intersection.type === Picks.INTERSECTED_ENTITY) {
|
||||
var entityProperties = Entities.getEntityProperties(objectID, DISPATCHER_PROPERTIES);
|
||||
var entityType = entityProperties.type;
|
||||
var isLocked = entityProperties.locked;
|
||||
if (entityType === "Web" && (!isLocked || triggerPressed)) {
|
||||
return intersectionType["WebEntity"];
|
||||
}
|
||||
}
|
||||
return intersectionType["None"];
|
||||
};
|
||||
|
||||
this.deleteContextOverlay = function() {
|
||||
var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND ?
|
||||
"RightFarActionGrabEntity" :
|
||||
"LeftFarActionGrabEntity");
|
||||
if (farGrabModule) {
|
||||
var entityWithContextOverlay = farGrabModule.entityWithContextOverlay;
|
||||
|
||||
if (entityWithContextOverlay) {
|
||||
ContextOverlay.destroyContextOverlay(entityWithContextOverlay);
|
||||
farGrabModule.entityWithContextOverlay = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.updateAlwaysOn = function(type) {
|
||||
var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser";
|
||||
this.parameters.handLaser.alwaysOn = (!Settings.getValue(PREFER_STYLUS_OVER_LASER, false) || type === intersectionType["HifiKeyboard"]);
|
||||
};
|
||||
|
||||
this.getDominantHand = function() {
|
||||
return MyAvatar.getDominantHand() === "right" ? 1 : 0;
|
||||
};
|
||||
|
||||
this.dominantHandOverride = false;
|
||||
|
||||
this.isReady = function (controllerData) {
|
||||
// Trivial rejection for when FarGrab is active.
|
||||
if (this.farGrabActive()) {
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE &&
|
||||
controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE;
|
||||
var type = this.getInteractableType(controllerData, isTriggerPressed, false);
|
||||
|
||||
if (type !== intersectionType["None"] && !this.grabModuleWantsNearbyOverlay(controllerData)) {
|
||||
if (type === intersectionType["WebOverlay"] || type === intersectionType["WebEntity"] || type === intersectionType["HifiTablet"]) {
|
||||
var otherModuleRunning = this.getOtherModule().running;
|
||||
otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand.
|
||||
var allowThisModule = !otherModuleRunning || isTriggerPressed;
|
||||
|
||||
if (!allowThisModule) {
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
|
||||
if (isTriggerPressed) {
|
||||
this.dominantHandOverride = true; // Override dominant hand.
|
||||
this.getOtherModule().dominantHandOverride = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateAlwaysOn(type);
|
||||
if (this.parameters.handLaser.alwaysOn || isTriggerPressed) {
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
if (Window.interstitialModeEnabled && Window.isPhysicsEnabled()) {
|
||||
this.restoreIgnoredObjects();
|
||||
}
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.shouldThisModuleRun = function(controllerData) {
|
||||
var otherModuleRunning = this.getOtherModule().running;
|
||||
otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand.
|
||||
otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand.
|
||||
var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData);
|
||||
// only allow for non-near grab
|
||||
return !otherModuleRunning && !grabModuleNeedsToRun;
|
||||
};
|
||||
|
||||
this.run = function(controllerData, deltaTime) {
|
||||
this.addObjectToIgnoreList(controllerData);
|
||||
var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE;
|
||||
var type = this.getInteractableType(controllerData, isTriggerPressed, false);
|
||||
var laserOn = isTriggerPressed || this.parameters.handLaser.alwaysOn;
|
||||
this.addObjectToIgnoreList(controllerData);
|
||||
|
||||
if (type === intersectionType["HifiTablet"] && laserOn) {
|
||||
if (this.shouldThisModuleRun(controllerData)) {
|
||||
this.running = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
} else if ((type === intersectionType["WebOverlay"] || type === intersectionType["WebEntity"]) && laserOn) { // auto laser on WebEntities andWebOverlays
|
||||
if (this.shouldThisModuleRun(controllerData)) {
|
||||
this.running = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
} else if ((type === intersectionType["HifiKeyboard"] && laserOn) || type === intersectionType["Overlay"]) {
|
||||
this.running = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
}
|
||||
|
||||
this.deleteContextOverlay();
|
||||
this.running = false;
|
||||
this.dominantHandOverride = false;
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
}
|
||||
|
||||
var leftOverlayLaserInput = new WebSurfaceLaserInput(LEFT_HAND);
|
||||
var rightOverlayLaserInput = new WebSurfaceLaserInput(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftWebSurfaceLaserInput", leftOverlayLaserInput);
|
||||
enableDispatcherModule("RightWebSurfaceLaserInput", rightOverlayLaserInput);
|
||||
|
||||
function cleanup() {
|
||||
disableDispatcherModule("LeftWebSurfaceLaserInput");
|
||||
disableDispatcherModule("RightWebSurfaceLaserInput");
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}());
|
62
scripts/simplifiedUI/system/controllers/controllerScripts.js
Normal file
62
scripts/simplifiedUI/system/controllers/controllerScripts.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
"use strict";
|
||||
|
||||
// controllerScripts.js
|
||||
//
|
||||
// Created by David Rowe on 15 Mar 2017.
|
||||
// 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, Menu */
|
||||
|
||||
var CONTOLLER_SCRIPTS = [
|
||||
"squeezeHands.js",
|
||||
"controllerDisplayManager.js",
|
||||
"grab.js",
|
||||
//"toggleAdvancedMovementForHandControllers.js",
|
||||
"handTouch.js",
|
||||
"controllerDispatcher.js",
|
||||
"controllerModules/nearParentGrabOverlay.js",
|
||||
"controllerModules/stylusInput.js",
|
||||
"controllerModules/equipEntity.js",
|
||||
"controllerModules/nearTrigger.js",
|
||||
"controllerModules/webSurfaceLaserInput.js",
|
||||
"controllerModules/inEditMode.js",
|
||||
"controllerModules/inVREditMode.js",
|
||||
"controllerModules/disableOtherModule.js",
|
||||
"controllerModules/farTrigger.js",
|
||||
"controllerModules/teleport.js",
|
||||
"controllerModules/hudOverlayPointer.js",
|
||||
"controllerModules/mouseHMD.js",
|
||||
"controllerModules/nearGrabHyperLinkEntity.js",
|
||||
"controllerModules/nearTabletHighlight.js",
|
||||
"controllerModules/nearGrabEntity.js",
|
||||
"controllerModules/farGrabEntity.js",
|
||||
"controllerModules/pushToTalk.js"
|
||||
];
|
||||
|
||||
var DEBUG_MENU_ITEM = "Debug defaultScripts.js";
|
||||
|
||||
function runDefaultsTogether() {
|
||||
for (var j in CONTOLLER_SCRIPTS) {
|
||||
if (CONTOLLER_SCRIPTS.hasOwnProperty(j)) {
|
||||
Script.include(CONTOLLER_SCRIPTS[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runDefaultsSeparately() {
|
||||
for (var i in CONTOLLER_SCRIPTS) {
|
||||
if (CONTOLLER_SCRIPTS.hasOwnProperty(i)) {
|
||||
Script.load(CONTOLLER_SCRIPTS[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Menu.isOptionChecked(DEBUG_MENU_ITEM)) {
|
||||
runDefaultsSeparately();
|
||||
} else {
|
||||
runDefaultsTogether();
|
||||
}
|
116
scripts/simplifiedUI/system/controllers/godView.js
Normal file
116
scripts/simplifiedUI/system/controllers/godView.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
"use strict";
|
||||
//
|
||||
// godView.js
|
||||
// scripts/system/
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1 Jun 2017
|
||||
// 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
|
||||
//
|
||||
/* globals HMD, Script, Menu, Tablet, Camera */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var godView = false;
|
||||
|
||||
var GOD_CAMERA_OFFSET = -1; // 1 meter below the avatar
|
||||
var GOD_VIEW_HEIGHT = 300; // 300 meter above the ground
|
||||
var ABOVE_GROUND_DROP = 2;
|
||||
var MOVE_BY = 1;
|
||||
|
||||
function moveTo(position) {
|
||||
if (godView) {
|
||||
MyAvatar.position = position;
|
||||
Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0});
|
||||
} else {
|
||||
MyAvatar.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
if (godView) {
|
||||
switch(event.text) {
|
||||
case "UP":
|
||||
moveTo(Vec3.sum(MyAvatar.position, {x:0.0, y: 0, z: -1 * MOVE_BY}));
|
||||
break;
|
||||
case "DOWN":
|
||||
moveTo(Vec3.sum(MyAvatar.position, {x:0, y: 0, z: MOVE_BY}));
|
||||
break;
|
||||
case "LEFT":
|
||||
moveTo(Vec3.sum(MyAvatar.position, {x:-1 * MOVE_BY, y: 0, z: 0}));
|
||||
break;
|
||||
case "RIGHT":
|
||||
moveTo(Vec3.sum(MyAvatar.position, {x:MOVE_BY, y: 0, z: 0}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mousePress(event) {
|
||||
if (godView) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,300));
|
||||
var moveToPosition = { x: pointingAt.x, y: MyAvatar.position.y, z: pointingAt.z };
|
||||
moveTo(moveToPosition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var oldCameraMode = Camera.mode;
|
||||
|
||||
function startGodView() {
|
||||
if (!godView) {
|
||||
oldCameraMode = Camera.mode;
|
||||
MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_VIEW_HEIGHT, z: 0});
|
||||
Camera.mode = "independent";
|
||||
Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0});
|
||||
Camera.orientation = Quat.fromPitchYawRollDegrees(-90,0,0);
|
||||
godView = true;
|
||||
}
|
||||
}
|
||||
|
||||
function endGodView() {
|
||||
if (godView) {
|
||||
Camera.mode = oldCameraMode;
|
||||
MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: (-1 * GOD_VIEW_HEIGHT) + ABOVE_GROUND_DROP, z: 0});
|
||||
godView = false;
|
||||
}
|
||||
}
|
||||
|
||||
var button;
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
function onClicked() {
|
||||
if (godView) {
|
||||
endGodView();
|
||||
} else {
|
||||
startGodView();
|
||||
}
|
||||
}
|
||||
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/switch-desk-i.svg", // FIXME - consider a better icon from Alan
|
||||
text: "God View"
|
||||
});
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.mousePressEvent.connect(mousePress);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
if (godView) {
|
||||
endGodView();
|
||||
}
|
||||
button.clicked.disconnect(onClicked);
|
||||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
Controller.keyPressEvent.disconnect(keyPressEvent);
|
||||
Controller.mousePressEvent.disconnect(mousePress);
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
522
scripts/simplifiedUI/system/controllers/grab.js
Normal file
522
scripts/simplifiedUI/system/controllers/grab.js
Normal file
|
@ -0,0 +1,522 @@
|
|||
"use strict";
|
||||
|
||||
// grab.js
|
||||
// examples
|
||||
//
|
||||
// Created by Eric Levin on May 1, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Grab's physically moveable entities with the mouse, by applying a spring force.
|
||||
//
|
||||
// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* global MyAvatar, Entities, Script, HMD, Camera, Vec3, Reticle, Overlays, Messages, Quat, Controller,
|
||||
isInEditMode, entityIsGrabbable, Picks, PickType, Pointers, unhighlightTargetEntity, DISPATCHER_PROPERTIES,
|
||||
entityIsGrabbable, getMainTabletIDs
|
||||
*/
|
||||
/* jslint bitwise: true */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
Script.include("/~/system/libraries/utils.js");
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
||||
var MOUSE_GRAB_JOINT = 65526; // FARGRAB_MOUSE_INDEX
|
||||
|
||||
var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed
|
||||
|
||||
var DELAY_FOR_30HZ = 33; // milliseconds
|
||||
|
||||
var ZERO_VEC3 = { x: 0, y: 0, z: 0 };
|
||||
var IDENTITY_QUAT = { x: 0, y: 0, z: 0, w: 1 };
|
||||
|
||||
// helper function
|
||||
function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistance) {
|
||||
var cameraPosition = Camera.getPosition();
|
||||
var localPointOnPlane = Vec3.subtract(pointOnPlane, cameraPosition);
|
||||
var distanceFromPlane = Vec3.dot(localPointOnPlane, planeNormal);
|
||||
var MIN_DISTANCE_FROM_PLANE = 0.001;
|
||||
if (Math.abs(distanceFromPlane) < MIN_DISTANCE_FROM_PLANE) {
|
||||
// camera is touching the plane
|
||||
return pointOnPlane;
|
||||
}
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var dirDotNorm = Vec3.dot(pickRay.direction, planeNormal);
|
||||
var MIN_RAY_PLANE_DOT = 0.00001;
|
||||
|
||||
var localIntersection;
|
||||
var useMaxForwardGrab = false;
|
||||
if (Math.abs(dirDotNorm) > MIN_RAY_PLANE_DOT) {
|
||||
var distanceToIntersection = distanceFromPlane / dirDotNorm;
|
||||
if (distanceToIntersection > 0 && distanceToIntersection < maxDistance) {
|
||||
// ray points into the plane
|
||||
localIntersection = Vec3.multiply(pickRay.direction, distanceFromPlane / dirDotNorm);
|
||||
} else {
|
||||
// ray intersects BEHIND the camera or else very far away
|
||||
// so we clamp the grab point to be the maximum forward position
|
||||
useMaxForwardGrab = true;
|
||||
}
|
||||
} else {
|
||||
// ray points perpendicular to grab plane
|
||||
// so we map the grab point to the maximum forward position
|
||||
useMaxForwardGrab = true;
|
||||
}
|
||||
if (useMaxForwardGrab) {
|
||||
// we re-route the intersection to be in front at max distance.
|
||||
var rayDirection = Vec3.subtract(pickRay.direction, Vec3.multiply(planeNormal, dirDotNorm));
|
||||
rayDirection = Vec3.normalize(rayDirection);
|
||||
localIntersection = Vec3.multiply(rayDirection, maxDistance);
|
||||
localIntersection = Vec3.sum(localIntersection, Vec3.multiply(planeNormal, distanceFromPlane));
|
||||
}
|
||||
var worldIntersection = Vec3.sum(cameraPosition, localIntersection);
|
||||
return worldIntersection;
|
||||
}
|
||||
|
||||
// Mouse class stores mouse click and drag info
|
||||
function Mouse() {
|
||||
this.current = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this.previous = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this.rotateStart = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this.cursorRestore = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
Mouse.prototype.startDrag = function(position) {
|
||||
this.current = {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
};
|
||||
this.startRotateDrag();
|
||||
};
|
||||
|
||||
Mouse.prototype.updateDrag = function(position) {
|
||||
this.current = {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
};
|
||||
};
|
||||
|
||||
Mouse.prototype.startRotateDrag = function() {
|
||||
this.previous = {
|
||||
x: this.current.x,
|
||||
y: this.current.y
|
||||
};
|
||||
this.rotateStart = {
|
||||
x: this.current.x,
|
||||
y: this.current.y
|
||||
};
|
||||
this.cursorRestore = Reticle.getPosition();
|
||||
};
|
||||
|
||||
Mouse.prototype.getDrag = function() {
|
||||
var delta = {
|
||||
x: this.current.x - this.previous.x,
|
||||
y: this.current.y - this.previous.y
|
||||
};
|
||||
this.previous = {
|
||||
x: this.current.x,
|
||||
y: this.current.y
|
||||
};
|
||||
return delta;
|
||||
};
|
||||
|
||||
Mouse.prototype.restoreRotateCursor = function() {
|
||||
Reticle.setPosition(this.cursorRestore);
|
||||
this.current = {
|
||||
x: this.rotateStart.x,
|
||||
y: this.rotateStart.y
|
||||
};
|
||||
};
|
||||
|
||||
var mouse = new Mouse();
|
||||
|
||||
var beacon = {
|
||||
type: "cube",
|
||||
dimensions: {
|
||||
x: 0.01,
|
||||
y: 0,
|
||||
z: 0.01
|
||||
},
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
},
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true
|
||||
};
|
||||
|
||||
// TODO: play sounds again when we aren't leaking AudioInjector threads
|
||||
// var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav");
|
||||
// var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav");
|
||||
// var VOLUME = 0.0;
|
||||
|
||||
|
||||
// Grabber class stores and computes info for grab behavior
|
||||
function Grabber() {
|
||||
this.isGrabbing = false;
|
||||
this.entityID = null;
|
||||
this.startPosition = ZERO_VEC3;
|
||||
this.lastRotation = IDENTITY_QUAT;
|
||||
this.currentPosition = ZERO_VEC3;
|
||||
this.planeNormal = ZERO_VEC3;
|
||||
|
||||
// maxDistance is a function of the size of the object.
|
||||
this.maxDistance = 0;
|
||||
|
||||
// mode defines the degrees of freedom of the grab target positions
|
||||
// relative to startPosition options include:
|
||||
// xzPlane (default)
|
||||
// verticalCylinder (SHIFT)
|
||||
// rotate (CONTROL)
|
||||
this.mode = "xzplane";
|
||||
|
||||
// offset allows the user to grab an object off-center. It points from the object's center
|
||||
// to the point where the ray intersects the grab plane (at the moment the grab is initiated).
|
||||
// Future target positions of the ray intersection are on the same plane, and the offset is subtracted
|
||||
// to compute the target position of the object's center.
|
||||
this.offset = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
this.liftKey = false; // SHIFT
|
||||
this.rotateKey = false; // CONTROL
|
||||
|
||||
this.mouseRayOverlays = Picks.createPick(PickType.Ray, {
|
||||
joint: "Mouse",
|
||||
filter: Picks.PICK_OVERLAYS | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
enabled: true
|
||||
});
|
||||
var tabletItems = getMainTabletIDs();
|
||||
if (tabletItems.length > 0) {
|
||||
Picks.setIncludeItems(this.mouseRayOverlays, tabletItems);
|
||||
}
|
||||
var renderStates = [{name: "grabbed", end: beacon}];
|
||||
this.mouseRayEntities = Pointers.createPointer(PickType.Ray, {
|
||||
joint: "Mouse",
|
||||
filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
faceAvatar: true,
|
||||
scaleWithParent: true,
|
||||
enabled: true,
|
||||
renderStates: renderStates
|
||||
});
|
||||
}
|
||||
|
||||
Grabber.prototype.computeNewGrabPlane = function() {
|
||||
if (!this.isGrabbing) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modeWasRotate = (this.mode == "rotate");
|
||||
this.mode = "xzPlane";
|
||||
this.planeNormal = {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
};
|
||||
if (this.rotateKey) {
|
||||
this.mode = "rotate";
|
||||
mouse.startRotateDrag();
|
||||
} else {
|
||||
if (modeWasRotate) {
|
||||
// we reset the mouse screen position whenever we stop rotating
|
||||
mouse.restoreRotateCursor();
|
||||
}
|
||||
if (this.liftKey) {
|
||||
this.mode = "verticalCylinder";
|
||||
// NOTE: during verticalCylinder mode a new planeNormal will be computed each move
|
||||
}
|
||||
}
|
||||
|
||||
this.pointOnPlane = Vec3.subtract(this.currentPosition, this.offset);
|
||||
var xzOffset = Vec3.subtract(this.pointOnPlane, Camera.getPosition());
|
||||
xzOffset.y = 0;
|
||||
this.xzDistanceToGrab = Vec3.length(xzOffset);
|
||||
};
|
||||
|
||||
Grabber.prototype.pressEvent = function(event) {
|
||||
if (isInEditMode() || HMD.active) {
|
||||
return;
|
||||
}
|
||||
if (event.button !== "LEFT") {
|
||||
return;
|
||||
}
|
||||
if (event.isAlt || event.isMeta) {
|
||||
return;
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint(Reticle.position) > 0) {
|
||||
// the mouse is pointing at an overlay; don't look for entities underneath the overlay.
|
||||
return;
|
||||
}
|
||||
|
||||
var overlayResult = Picks.getPrevPickResult(this.mouseRayOverlays);
|
||||
if (overlayResult.type != Picks.INTERSECTED_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pickResults = Pointers.getPrevPickResult(this.mouseRayEntities);
|
||||
if (pickResults.type == Picks.INTERSECTED_NONE) {
|
||||
Pointers.setRenderState(this.mouseRayEntities, "");
|
||||
return;
|
||||
}
|
||||
|
||||
var props = Entities.getEntityProperties(pickResults.objectID, DISPATCHER_PROPERTIES);
|
||||
if (!entityIsGrabbable(props)) {
|
||||
// only grab grabbable objects
|
||||
return;
|
||||
}
|
||||
if (props.grab.equippable) {
|
||||
// don't mouse-grab click-to-equip entities (let equipEntity.js handle these)
|
||||
return;
|
||||
}
|
||||
|
||||
Pointers.setRenderState(this.mouseRayEntities, "grabbed");
|
||||
Pointers.setLockEndUUID(this.mouseRayEntities, pickResults.objectID, false);
|
||||
unhighlightTargetEntity(pickResults.objectID);
|
||||
|
||||
mouse.startDrag(event);
|
||||
|
||||
var clickedEntity = pickResults.objectID;
|
||||
var entityProperties = Entities.getEntityProperties(clickedEntity, DISPATCHER_PROPERTIES);
|
||||
this.startPosition = entityProperties.position;
|
||||
this.lastRotation = entityProperties.rotation;
|
||||
var cameraPosition = Camera.getPosition();
|
||||
|
||||
var objectBoundingDiameter = Vec3.length(entityProperties.dimensions);
|
||||
beacon.dimensions.y = objectBoundingDiameter;
|
||||
Pointers.editRenderState(this.mouseRayEntities, "grabbed", {end: beacon});
|
||||
this.maxDistance = objectBoundingDiameter / MAX_SOLID_ANGLE;
|
||||
if (Vec3.distance(this.startPosition, cameraPosition) > this.maxDistance) {
|
||||
// don't allow grabs of things far away
|
||||
return;
|
||||
}
|
||||
|
||||
this.isGrabbing = true;
|
||||
|
||||
this.entityID = clickedEntity;
|
||||
this.currentPosition = entityProperties.position;
|
||||
|
||||
// compute the grab point
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var nearestPoint = Vec3.subtract(this.startPosition, cameraPosition);
|
||||
var distanceToGrab = Vec3.dot(nearestPoint, pickRay.direction);
|
||||
nearestPoint = Vec3.multiply(distanceToGrab, pickRay.direction);
|
||||
this.pointOnPlane = Vec3.sum(cameraPosition, nearestPoint);
|
||||
|
||||
// compute the grab offset (points from point of grab to object center)
|
||||
this.offset = Vec3.subtract(this.startPosition, this.pointOnPlane); // offset in world-space
|
||||
MyAvatar.setJointTranslation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointPoint(this.startPosition));
|
||||
MyAvatar.setJointRotation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointRotation(this.lastRotation));
|
||||
|
||||
this.computeNewGrabPlane();
|
||||
this.moveEvent(event);
|
||||
|
||||
var args = "mouse";
|
||||
Entities.callEntityMethod(this.entityID, "startDistanceGrab", args);
|
||||
|
||||
Messages.sendLocalMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'grab',
|
||||
grabbedEntity: this.entityID
|
||||
}));
|
||||
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
this.grabID = null;
|
||||
}
|
||||
this.grabID = MyAvatar.grab(this.entityID, MOUSE_GRAB_JOINT, ZERO_VEC3, IDENTITY_QUAT);
|
||||
|
||||
// TODO: play sounds again when we aren't leaking AudioInjector threads
|
||||
//Audio.playSound(grabSound, { position: entityProperties.position, volume: VOLUME });
|
||||
};
|
||||
|
||||
Grabber.prototype.releaseEvent = function(event) {
|
||||
if (event.button !== "LEFT" && !HMD.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.moveEventTimer) {
|
||||
Script.clearTimeout(this.moveEventTimer);
|
||||
this.moveEventTimer = null;
|
||||
}
|
||||
|
||||
if (this.isGrabbing) {
|
||||
this.isGrabbing = false;
|
||||
|
||||
Pointers.setRenderState(this.mouseRayEntities, "");
|
||||
Pointers.setLockEndUUID(this.mouseRayEntities, null, false);
|
||||
|
||||
var args = "mouse";
|
||||
Entities.callEntityMethod(this.entityID, "releaseGrab", args);
|
||||
|
||||
Messages.sendLocalMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'release',
|
||||
grabbedEntity: this.entityID,
|
||||
joint: "mouse"
|
||||
}));
|
||||
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
this.grabID = null;
|
||||
}
|
||||
|
||||
MyAvatar.clearJointData(MOUSE_GRAB_JOINT);
|
||||
|
||||
// TODO: play sounds again when we aren't leaking AudioInjector threads
|
||||
//Audio.playSound(releaseSound, { position: entityProperties.position, volume: VOLUME });
|
||||
}
|
||||
};
|
||||
|
||||
Grabber.prototype.scheduleMouseMoveProcessor = function(event) {
|
||||
var _this = this;
|
||||
if (!this.moveEventTimer) {
|
||||
this.moveEventTimer = Script.setTimeout(function() {
|
||||
_this.moveEventProcess();
|
||||
}, DELAY_FOR_30HZ);
|
||||
}
|
||||
};
|
||||
|
||||
Grabber.prototype.moveEvent = function(event) {
|
||||
// during the handling of the event, do as little as possible. We save the updated mouse position,
|
||||
// and start a timer to react to the change. If more changes arrive before the timer fires, only
|
||||
// the last update will be considered. This is done to avoid backing-up Qt's event queue.
|
||||
if (!this.isGrabbing || HMD.active) {
|
||||
return;
|
||||
}
|
||||
mouse.updateDrag(event);
|
||||
this.scheduleMouseMoveProcessor();
|
||||
};
|
||||
|
||||
Grabber.prototype.moveEventProcess = function() {
|
||||
this.moveEventTimer = null;
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID, DISPATCHER_PROPERTIES);
|
||||
if (!entityProperties || HMD.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentPosition = entityProperties.position;
|
||||
|
||||
if (this.mode === "rotate") {
|
||||
var drag = mouse.getDrag();
|
||||
var orientation = Camera.getOrientation();
|
||||
var dragOffset = Vec3.multiply(drag.x, Quat.getRight(orientation));
|
||||
dragOffset = Vec3.sum(dragOffset, Vec3.multiply(-drag.y, Quat.getUp(orientation)));
|
||||
var axis = Vec3.cross(dragOffset, Quat.getForward(orientation));
|
||||
axis = Vec3.normalize(axis);
|
||||
var ROTATE_STRENGTH = 0.4; // magic number tuned by hand
|
||||
var angle = ROTATE_STRENGTH * Math.sqrt((drag.x * drag.x) + (drag.y * drag.y));
|
||||
var deltaQ = Quat.angleAxis(angle, axis);
|
||||
|
||||
this.lastRotation = Quat.multiply(deltaQ, this.lastRotation);
|
||||
MyAvatar.setJointRotation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointRotation(this.lastRotation));
|
||||
|
||||
} else {
|
||||
var newPointOnPlane;
|
||||
|
||||
if (this.mode === "verticalCylinder") {
|
||||
// for this mode we recompute the plane based on current Camera
|
||||
var planeNormal = Quat.getForward(Camera.getOrientation());
|
||||
planeNormal.y = 0;
|
||||
planeNormal = Vec3.normalize(planeNormal);
|
||||
var pointOnCylinder = Vec3.multiply(planeNormal, this.xzDistanceToGrab);
|
||||
pointOnCylinder = Vec3.sum(Camera.getPosition(), pointOnCylinder);
|
||||
newPointOnPlane = mouseIntersectionWithPlane(pointOnCylinder, planeNormal, mouse.current, this.maxDistance);
|
||||
} else {
|
||||
var cameraPosition = Camera.getPosition();
|
||||
newPointOnPlane = mouseIntersectionWithPlane(this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance);
|
||||
var relativePosition = Vec3.subtract(newPointOnPlane, cameraPosition);
|
||||
var distance = Vec3.length(relativePosition);
|
||||
if (distance > this.maxDistance) {
|
||||
// clamp distance
|
||||
relativePosition = Vec3.multiply(relativePosition, this.maxDistance / distance);
|
||||
newPointOnPlane = Vec3.sum(relativePosition, cameraPosition);
|
||||
}
|
||||
}
|
||||
|
||||
MyAvatar.setJointTranslation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointPoint(Vec3.sum(newPointOnPlane, this.offset)));
|
||||
}
|
||||
|
||||
this.scheduleMouseMoveProcessor();
|
||||
};
|
||||
|
||||
Grabber.prototype.keyReleaseEvent = function(event) {
|
||||
if (event.text === "SHIFT") {
|
||||
this.liftKey = false;
|
||||
}
|
||||
if (event.text === "CONTROL") {
|
||||
this.rotateKey = false;
|
||||
}
|
||||
this.computeNewGrabPlane();
|
||||
};
|
||||
|
||||
Grabber.prototype.keyPressEvent = function(event) {
|
||||
if (event.text === "SHIFT") {
|
||||
this.liftKey = true;
|
||||
}
|
||||
if (event.text === "CONTROL") {
|
||||
this.rotateKey = true;
|
||||
}
|
||||
this.computeNewGrabPlane();
|
||||
};
|
||||
|
||||
Grabber.prototype.cleanup = function() {
|
||||
Pointers.removePointer(this.mouseRayEntities);
|
||||
Picks.removePick(this.mouseRayOverlays);
|
||||
if (this.grabID) {
|
||||
MyAvatar.releaseGrab(this.grabID);
|
||||
this.grabID = null;
|
||||
}
|
||||
};
|
||||
|
||||
var grabber = new Grabber();
|
||||
|
||||
function pressEvent(event) {
|
||||
grabber.pressEvent(event);
|
||||
}
|
||||
|
||||
function moveEvent(event) {
|
||||
grabber.moveEvent(event);
|
||||
}
|
||||
|
||||
function releaseEvent(event) {
|
||||
grabber.releaseEvent(event);
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
grabber.keyPressEvent(event);
|
||||
}
|
||||
|
||||
function keyReleaseEvent(event) {
|
||||
grabber.keyReleaseEvent(event);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
grabber.cleanup();
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(pressEvent);
|
||||
Controller.mouseMoveEvent.connect(moveEvent);
|
||||
Controller.mouseReleaseEvent.connect(releaseEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
958
scripts/simplifiedUI/system/controllers/handTouch.js
Normal file
958
scripts/simplifiedUI/system/controllers/handTouch.js
Normal file
|
@ -0,0 +1,958 @@
|
|||
//
|
||||
// scripts/system/libraries/handTouch.js
|
||||
//
|
||||
// Created by Luis Cuenca on 12/29/17
|
||||
// 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
|
||||
//
|
||||
|
||||
/* jslint bitwise: true */
|
||||
|
||||
/* global Script, Overlays, Controller, Vec3, MyAvatar, Entities, RayPick
|
||||
*/
|
||||
|
||||
(function () {
|
||||
|
||||
var LEAP_MOTION_NAME = "LeapMotion";
|
||||
var handTouchEnabled = true;
|
||||
var leapMotionEnabled = Controller.getRunningInputDeviceNames().indexOf(LEAP_MOTION_NAME) >= 0;
|
||||
var MSECONDS_AFTER_LOAD = 2000;
|
||||
var updateFingerWithIndex = 0;
|
||||
var untouchableEntities = [];
|
||||
|
||||
// Keys to access finger data
|
||||
var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"];
|
||||
|
||||
// Additionally close the hands to achieve a grabbing effect
|
||||
var grabPercent = { left: 0, right: 0 };
|
||||
|
||||
var Palm = function() {
|
||||
this.position = {x: 0, y: 0, z: 0};
|
||||
this.perpendicular = {x: 0, y: 0, z: 0};
|
||||
this.distance = 0;
|
||||
this.fingers = {
|
||||
pinky: {x: 0, y: 0, z: 0},
|
||||
middle: {x: 0, y: 0, z: 0},
|
||||
ring: {x: 0, y: 0, z: 0},
|
||||
thumb: {x: 0, y: 0, z: 0},
|
||||
index: {x: 0, y: 0, z: 0}
|
||||
};
|
||||
this.set = false;
|
||||
};
|
||||
|
||||
var palmData = {
|
||||
left: new Palm(),
|
||||
right: new Palm()
|
||||
};
|
||||
|
||||
var handJointNames = {left: "LeftHand", right: "RightHand"};
|
||||
|
||||
// Store which fingers are touching - if all false restate the default poses
|
||||
var isTouching = {
|
||||
left: {
|
||||
pinky: false,
|
||||
middle: false,
|
||||
ring: false,
|
||||
thumb: false,
|
||||
index: false
|
||||
}, right: {
|
||||
pinky: false,
|
||||
middle: false,
|
||||
ring: false,
|
||||
thumb: false,
|
||||
index: false
|
||||
}
|
||||
};
|
||||
|
||||
// frame count for transition to default pose
|
||||
|
||||
var countToDefault = {
|
||||
left: 0,
|
||||
right: 0
|
||||
};
|
||||
|
||||
// joint data for open pose
|
||||
var dataOpen = {
|
||||
left: {
|
||||
pinky: [
|
||||
{x: -0.0066, y: -0.0224, z: -0.2174, w: 0.9758},
|
||||
{x: 0.0112, y: 0.0001, z: 0.0093, w: 0.9999},
|
||||
{x: -0.0346, y: 0.0003, z: -0.0073, w: 0.9994}
|
||||
],
|
||||
ring: [
|
||||
{x: -0.0029, y: -0.0094, z: -0.1413, w: 0.9899},
|
||||
{x: 0.0112, y: 0.0001, z: 0.0059, w: 0.9999},
|
||||
{x: -0.0346, y: 0.0002, z: -0.006, w: 0.9994}
|
||||
],
|
||||
middle: [
|
||||
{x: -0.0016, y: 0, z: -0.0286, w: 0.9996},
|
||||
{x: 0.0112, y: -0.0001, z: -0.0063, w: 0.9999},
|
||||
{x: -0.0346, y: -0.0003, z: 0.0073, w: 0.9994}
|
||||
],
|
||||
index: [
|
||||
{x: -0.0016, y: 0.0001, z: 0.0199, w: 0.9998},
|
||||
{x: 0.0112, y: 0, z: 0.0081, w: 0.9999},
|
||||
{x: -0.0346, y: 0.0008, z: -0.023, w: 0.9991}
|
||||
],
|
||||
thumb: [
|
||||
{x: 0.0354, y: 0.0363, z: 0.3275, w: 0.9435},
|
||||
{x: -0.0945, y: 0.0938, z: 0.0995, w: 0.9861},
|
||||
{x: -0.0952, y: 0.0718, z: 0.1382, w: 0.9832}
|
||||
]
|
||||
}, right: {
|
||||
pinky: [
|
||||
{x: -0.0034, y: 0.023, z: 0.1051, w: 0.9942},
|
||||
{x: 0.0106, y: -0.0001, z: -0.0091, w: 0.9999},
|
||||
{x: -0.0346, y: -0.0003, z: 0.0075, w: 0.9994}
|
||||
],
|
||||
ring: [
|
||||
{x: -0.0013, y: 0.0097, z: 0.0311, w: 0.9995},
|
||||
{x: 0.0106, y: -0.0001, z: -0.0056, w: 0.9999},
|
||||
{x: -0.0346, y: -0.0002, z: 0.0061, w: 0.9994}
|
||||
],
|
||||
middle: [
|
||||
{x: -0.001, y: 0, z: 0.0285, w: 0.9996},
|
||||
{x: 0.0106, y: 0.0001, z: 0.0062, w: 0.9999},
|
||||
{x: -0.0346, y: 0.0003, z: -0.0074, w: 0.9994}
|
||||
],
|
||||
index: [
|
||||
{x: -0.001, y: 0, z: -0.0199, w: 0.9998},
|
||||
{x: 0.0106, y: -0.0001, z: -0.0079, w: 0.9999},
|
||||
{x: -0.0346, y: -0.0008, z: 0.0229, w: 0.9991}
|
||||
],
|
||||
thumb: [
|
||||
{x: 0.0355, y: -0.0363, z: -0.3263, w: 0.9439},
|
||||
{x: -0.0946, y: -0.0938, z: -0.0996, w: 0.9861},
|
||||
{x: -0.0952, y: -0.0719, z: -0.1376, w: 0.9833}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// joint data for close pose
|
||||
var dataClose = {
|
||||
left: {
|
||||
pinky: [
|
||||
{x: 0.5878, y: -0.1735, z: -0.1123, w: 0.7821},
|
||||
{x: 0.5704, y: 0.0053, z: 0.0076, w: 0.8213},
|
||||
{x: 0.6069, y: -0.0044, z: -0.0058, w: 0.7947}
|
||||
],
|
||||
ring: [
|
||||
{x: 0.5761, y: -0.0989, z: -0.1025, w: 0.8048},
|
||||
{x: 0.5332, y: 0.0032, z: 0.005, w: 0.846},
|
||||
{x: 0.5773, y: -0.0035, z: -0.0049, w: 0.8165}
|
||||
],
|
||||
middle: [
|
||||
{x: 0.543, y: -0.0469, z: -0.0333, w: 0.8378},
|
||||
{x: 0.5419, y: -0.0034, z: -0.0053, w: 0.8404},
|
||||
{x: 0.5015, y: 0.0037, z: 0.0063, w: 0.8651}
|
||||
],
|
||||
index: [
|
||||
{x: 0.3051, y: -0.0156, z: -0.014, w: 0.9521},
|
||||
{x: 0.6414, y: 0.0051, z: 0.0063, w: 0.7671},
|
||||
{x: 0.5646, y: -0.013, z: -0.019, w: 0.8251}
|
||||
],
|
||||
thumb: [
|
||||
{x: 0.313, y: -0.0348, z: 0.3192, w: 0.8938},
|
||||
{x: 0, y: 0, z: -0.37, w: 0.929},
|
||||
{x: 0, y: 0, z: -0.2604, w: 0.9655}
|
||||
]
|
||||
}, right: {
|
||||
pinky: [
|
||||
{x: 0.5881, y: 0.1728, z: 0.1114, w: 0.7823},
|
||||
{x: 0.5704, y: -0.0052, z: -0.0075, w: 0.8213},
|
||||
{x: 0.6069, y: 0.0046, z: 0.006, w: 0.7947}
|
||||
],
|
||||
ring: [
|
||||
{x: 0.5729, y: 0.1181, z: 0.0898, w: 0.8061},
|
||||
{x: 0.5332, y: -0.003, z: -0.0048, w: 0.846},
|
||||
{x: 0.5773, y: 0.0035, z: 0.005, w: 0.8165}
|
||||
],
|
||||
middle: [
|
||||
{x: 0.543, y: 0.0468, z: 0.0332, w: 0.8378},
|
||||
{x: 0.5419, y: 0.0034, z: 0.0052, w: 0.8404},
|
||||
{x: 0.5047, y: -0.0037, z: -0.0064, w: 0.8632}
|
||||
],
|
||||
index: [
|
||||
{x: 0.306, y: -0.0076, z: -0.0584, w: 0.9502},
|
||||
{x: 0.6409, y: -0.005, z: -0.006, w: 0.7675},
|
||||
{x: 0.5646, y: 0.0129, z: 0.0189, w: 0.8251}
|
||||
],
|
||||
thumb: [
|
||||
{x: 0.313, y: 0.0352, z: -0.3181, w: 0.8942},
|
||||
{x: 0, y: 0, z: 0.3698, w: 0.9291},
|
||||
{x: 0, y: 0, z: 0.2609, w: 0.9654}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// snapshot for the default pose
|
||||
var dataDefault = {
|
||||
left: {
|
||||
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
set: false
|
||||
},
|
||||
right: {
|
||||
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
set: false
|
||||
}
|
||||
};
|
||||
|
||||
// joint data for the current frame
|
||||
var dataCurrent = {
|
||||
left: {
|
||||
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
|
||||
},
|
||||
right: {
|
||||
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
|
||||
}
|
||||
};
|
||||
|
||||
// interpolated values on joint data to smooth movement
|
||||
var dataDelta = {
|
||||
left: {
|
||||
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
|
||||
},
|
||||
right: {
|
||||
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
|
||||
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
|
||||
}
|
||||
};
|
||||
|
||||
// Acquire an updated value per hand every 5 frames when finger is touching (faster in)
|
||||
var touchAnimationSteps = 5;
|
||||
|
||||
// Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out)
|
||||
var defaultAnimationSteps = 10;
|
||||
|
||||
// Debugging info
|
||||
var showSphere = false;
|
||||
var showLines = false;
|
||||
|
||||
// This get setup on creation
|
||||
var linesCreated = false;
|
||||
var sphereCreated = false;
|
||||
|
||||
// Register object with API Debugger
|
||||
var varsToDebug = {
|
||||
scriptLoaded: false,
|
||||
toggleDebugSphere: function() {
|
||||
showSphere = !showSphere;
|
||||
if (showSphere && !sphereCreated) {
|
||||
createDebugSphere();
|
||||
sphereCreated = true;
|
||||
}
|
||||
},
|
||||
toggleDebugLines: function() {
|
||||
showLines = !showLines;
|
||||
if (showLines && !linesCreated) {
|
||||
createDebugLines();
|
||||
linesCreated = true;
|
||||
}
|
||||
},
|
||||
fingerPercent: {
|
||||
left: {
|
||||
pinky: 0.38,
|
||||
middle: 0.38,
|
||||
ring: 0.38,
|
||||
thumb: 0.38,
|
||||
index: 0.38
|
||||
} ,
|
||||
right: {
|
||||
pinky: 0.38,
|
||||
middle: 0.38,
|
||||
ring: 0.38,
|
||||
thumb: 0.38,
|
||||
index: 0.38
|
||||
}
|
||||
},
|
||||
triggerValues: {
|
||||
leftTriggerValue: 0,
|
||||
leftTriggerClicked: 0,
|
||||
rightTriggerValue: 0,
|
||||
rightTriggerClicked: 0,
|
||||
leftSecondaryValue: 0,
|
||||
rightSecondaryValue: 0
|
||||
},
|
||||
palmData: {
|
||||
left: new Palm(),
|
||||
right: new Palm()
|
||||
},
|
||||
offset: {x: 0, y: 0, z: 0},
|
||||
avatarLoaded: false
|
||||
};
|
||||
|
||||
// Add/Subtract the joint data - per finger joint
|
||||
function addVals(val1, val2, sign) {
|
||||
var val = [];
|
||||
if (val1.length !== val2.length) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < val1.length; i++) {
|
||||
val.push({x: 0, y: 0, z: 0, w: 0});
|
||||
val[i].x = val1[i].x + sign*val2[i].x;
|
||||
val[i].y = val1[i].y + sign*val2[i].y;
|
||||
val[i].z = val1[i].z + sign*val2[i].z;
|
||||
val[i].w = val1[i].w + sign*val2[i].w;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// Multiply/Divide the joint data - per finger joint
|
||||
function multiplyValsBy(val1, num) {
|
||||
var val = [];
|
||||
for (var i = 0; i < val1.length; i++) {
|
||||
val.push({x: 0, y: 0, z: 0, w: 0});
|
||||
val[i].x = val1[i].x * num;
|
||||
val[i].y = val1[i].y * num;
|
||||
val[i].z = val1[i].z * num;
|
||||
val[i].w = val1[i].w * num;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// Calculate the finger lengths by adding its joint lengths
|
||||
function getJointDistances(jointNamesArray) {
|
||||
var result = {distances: [], totalDistance: 0};
|
||||
for (var i = 1; i < jointNamesArray.length; i++) {
|
||||
var index0 = MyAvatar.getJointIndex(jointNamesArray[i-1]);
|
||||
var index1 = MyAvatar.getJointIndex(jointNamesArray[i]);
|
||||
var pos0 = MyAvatar.getJointPosition(index0);
|
||||
var pos1 = MyAvatar.getJointPosition(index1);
|
||||
var distance = Vec3.distance(pos0, pos1);
|
||||
result.distances.push(distance);
|
||||
result.totalDistance += distance;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function dataRelativeToWorld(side, dataIn, dataOut) {
|
||||
var handJoint = handJointNames[side];
|
||||
var jointIndex = MyAvatar.getJointIndex(handJoint);
|
||||
var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex);
|
||||
|
||||
dataOut.position = MyAvatar.jointToWorldPoint(dataIn.position, jointIndex);
|
||||
var localPerpendicular = side === "right" ? {x: 0.2, y: 0, z: 1} : {x: -0.2, y: 0, z: 1};
|
||||
dataOut.perpendicular = Vec3.normalize(
|
||||
Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand)
|
||||
);
|
||||
dataOut.distance = dataIn.distance;
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
dataOut.fingers[finger] = MyAvatar.jointToWorldPoint(dataIn.fingers[finger], jointIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function dataRelativeToHandJoint(side, dataIn, dataOut) {
|
||||
var handJoint = handJointNames[side];
|
||||
var jointIndex = MyAvatar.getJointIndex(handJoint);
|
||||
var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex);
|
||||
|
||||
dataOut.position = MyAvatar.worldToJointPoint(dataIn.position, jointIndex);
|
||||
dataOut.perpendicular = MyAvatar.worldToJointPoint(Vec3.sum(worldPosHand, dataIn.perpendicular), jointIndex);
|
||||
dataOut.distance = dataIn.distance;
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
dataOut.fingers[finger] = MyAvatar.worldToJointPoint(dataIn.fingers[finger], jointIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate touch field; Sphere at the center of the palm,
|
||||
// perpendicular vector from the palm plane and origin of the the finger rays
|
||||
function estimatePalmData(side) {
|
||||
// Return data object
|
||||
var data = new Palm();
|
||||
|
||||
var jointOffset = { x: 0, y: 0, z: 0 };
|
||||
|
||||
var upperSide = side[0].toUpperCase() + side.substring(1);
|
||||
var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand");
|
||||
|
||||
// Store position of the hand joint
|
||||
var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand);
|
||||
var minusWorldPosHand = {x: -worldPosHand.x, y: -worldPosHand.y, z: -worldPosHand.z};
|
||||
|
||||
// Data for finger rays
|
||||
var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined};
|
||||
var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined};
|
||||
|
||||
var thumbLength = 0;
|
||||
var weightCount = 0;
|
||||
|
||||
// Calculate palm center
|
||||
var handJointWeight = 1;
|
||||
var fingerJointWeight = 2;
|
||||
|
||||
var palmCenter = {x: 0, y: 0, z: 0};
|
||||
palmCenter = Vec3.sum(worldPosHand, palmCenter);
|
||||
|
||||
weightCount += handJointWeight;
|
||||
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
var jointSuffixes = 4; // Get 4 joint names with suffix numbers (0, 1, 2, 3)
|
||||
var jointNames = getJointNames(side, finger, jointSuffixes);
|
||||
var fingerLength = getJointDistances(jointNames).totalDistance;
|
||||
|
||||
var jointIndex = MyAvatar.getJointIndex(jointNames[0]);
|
||||
positions[finger] = MyAvatar.jointToWorldPoint(jointOffset, jointIndex);
|
||||
directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand));
|
||||
data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger]));
|
||||
if (finger !== "thumb") {
|
||||
// finger joints have double the weight than the hand joint
|
||||
// This would better position the palm estimation
|
||||
|
||||
palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter);
|
||||
weightCount += fingerJointWeight;
|
||||
} else {
|
||||
thumbLength = fingerLength;
|
||||
}
|
||||
}
|
||||
|
||||
// perpendicular change direction depending on the side
|
||||
data.perpendicular = (side === "right") ?
|
||||
Vec3.normalize(Vec3.cross(directions.index, directions.pinky)):
|
||||
Vec3.normalize(Vec3.cross(directions.pinky, directions.index));
|
||||
|
||||
data.position = Vec3.multiply(1.0/weightCount, palmCenter);
|
||||
|
||||
if (side === "right") {
|
||||
varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand);
|
||||
}
|
||||
|
||||
var palmDistanceMultiplier = 1.55; // 1.55 based on test/error for the sphere radius that best fits the hand
|
||||
data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index);
|
||||
|
||||
// move back thumb ray origin
|
||||
var thumbBackMultiplier = 0.2;
|
||||
data.fingers.thumb = Vec3.sum(
|
||||
data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular));
|
||||
|
||||
// return getDataRelativeToHandJoint(side, data);
|
||||
dataRelativeToHandJoint(side, data, palmData[side]);
|
||||
palmData[side].set = true;
|
||||
}
|
||||
|
||||
// Register GlobalDebugger for API Debugger
|
||||
Script.registerValue("GlobalDebugger", varsToDebug);
|
||||
|
||||
// store the rays for the fingers - only for debug purposes
|
||||
var fingerRays = {
|
||||
left: {
|
||||
pinky: undefined,
|
||||
middle: undefined,
|
||||
ring: undefined,
|
||||
thumb: undefined,
|
||||
index: undefined
|
||||
},
|
||||
right: {
|
||||
pinky: undefined,
|
||||
middle: undefined,
|
||||
ring: undefined,
|
||||
thumb: undefined,
|
||||
index: undefined
|
||||
}
|
||||
};
|
||||
|
||||
// Create debug overlays - finger rays + palm rays + spheres
|
||||
var palmRay, sphereHand;
|
||||
|
||||
function createDebugLines() {
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
fingerRays.left[fingerKeys[i]] = Overlays.addOverlay("line3d", {
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
start: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 1, z: 0 },
|
||||
visible: showLines
|
||||
});
|
||||
fingerRays.right[fingerKeys[i]] = Overlays.addOverlay("line3d", {
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
start: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 1, z: 0 },
|
||||
visible: showLines
|
||||
});
|
||||
}
|
||||
|
||||
palmRay = {
|
||||
left: Overlays.addOverlay("line3d", {
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
start: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 1, z: 0 },
|
||||
visible: showLines
|
||||
}),
|
||||
right: Overlays.addOverlay("line3d", {
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
start: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 1, z: 0 },
|
||||
visible: showLines
|
||||
})
|
||||
};
|
||||
linesCreated = true;
|
||||
}
|
||||
|
||||
function createDebugSphere() {
|
||||
sphereHand = {
|
||||
right: Overlays.addOverlay("sphere", {
|
||||
position: MyAvatar.position,
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
scale: { x: 0.01, y: 0.01, z: 0.01 },
|
||||
visible: showSphere
|
||||
}),
|
||||
left: Overlays.addOverlay("sphere", {
|
||||
position: MyAvatar.position,
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
scale: { x: 0.01, y: 0.01, z: 0.01 },
|
||||
visible: showSphere
|
||||
})
|
||||
};
|
||||
sphereCreated = true;
|
||||
}
|
||||
|
||||
function acquireDefaultPose(side) {
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
var jointSuffixes = 3; // We need rotation of the 0, 1 and 2 joints
|
||||
var names = getJointNames(side, finger, jointSuffixes);
|
||||
for (var j = 0; j < names.length; j++) {
|
||||
var index = MyAvatar.getJointIndex(names[j]);
|
||||
var rotation = MyAvatar.getJointRotation(index);
|
||||
dataDefault[side][finger][j] = dataCurrent[side][finger][j] = rotation;
|
||||
}
|
||||
}
|
||||
dataDefault[side].set = true;
|
||||
}
|
||||
|
||||
var rayPicks = {
|
||||
left: {
|
||||
pinky: undefined,
|
||||
middle: undefined,
|
||||
ring: undefined,
|
||||
thumb: undefined,
|
||||
index: undefined
|
||||
},
|
||||
right: {
|
||||
pinky: undefined,
|
||||
middle: undefined,
|
||||
ring: undefined,
|
||||
thumb: undefined,
|
||||
index: undefined
|
||||
}
|
||||
};
|
||||
|
||||
var dataFailed = {
|
||||
left: {
|
||||
pinky: 0,
|
||||
middle: 0,
|
||||
ring: 0,
|
||||
thumb: 0,
|
||||
index: 0
|
||||
},
|
||||
right: {
|
||||
pinky: 0,
|
||||
middle: 0,
|
||||
ring: 0,
|
||||
thumb: 0,
|
||||
index: 0
|
||||
}
|
||||
};
|
||||
|
||||
function clearRayPicks(side) {
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
if (rayPicks[side][finger] !== undefined) {
|
||||
RayPick.removeRayPick(rayPicks[side][finger]);
|
||||
rayPicks[side][finger] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createRayPicks(side) {
|
||||
var data = palmData[side];
|
||||
clearRayPicks(side);
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
var LOOKUP_DISTANCE_MULTIPLIER = 1.5;
|
||||
var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance;
|
||||
var checkOffset = {
|
||||
x: data.perpendicular.x * dist,
|
||||
y: data.perpendicular.y * dist,
|
||||
z: data.perpendicular.z * dist
|
||||
};
|
||||
|
||||
var checkPoint = Vec3.sum(data.position, Vec3.multiply(2, checkOffset));
|
||||
var sensorToWorldScale = MyAvatar.getSensorToWorldScale();
|
||||
|
||||
var origin = data.fingers[finger];
|
||||
|
||||
var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin));
|
||||
|
||||
origin = Vec3.multiply(1/sensorToWorldScale, origin);
|
||||
|
||||
rayPicks[side][finger] = RayPick.createRayPick(
|
||||
{
|
||||
"enabled": false,
|
||||
"joint": handJointNames[side],
|
||||
"posOffset": origin,
|
||||
"dirOffset": direction,
|
||||
"filter": RayPick.PICK_ENTITIES
|
||||
}
|
||||
);
|
||||
|
||||
RayPick.setPrecisionPicking(rayPicks[side][finger], true);
|
||||
}
|
||||
}
|
||||
|
||||
function activateNextRay(side, index) {
|
||||
var nextIndex = (index < fingerKeys.length-1) ? index + 1 : 0;
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
if (i === nextIndex) {
|
||||
RayPick.enableRayPick(rayPicks[side][finger]);
|
||||
} else {
|
||||
RayPick.disableRayPick(rayPicks[side][finger]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSphereHand(side) {
|
||||
var data = new Palm();
|
||||
dataRelativeToWorld(side, palmData[side], data);
|
||||
varsToDebug.palmData[side] = palmData[side];
|
||||
|
||||
var palmPoint = data.position;
|
||||
var LOOKUP_DISTANCE_MULTIPLIER = 1.5;
|
||||
var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance;
|
||||
|
||||
// Situate the debugging overlays
|
||||
var checkOffset = {
|
||||
x: data.perpendicular.x * dist,
|
||||
y: data.perpendicular.y * dist,
|
||||
z: data.perpendicular.z * dist
|
||||
};
|
||||
|
||||
var spherePos = Vec3.sum(palmPoint, checkOffset);
|
||||
var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset));
|
||||
|
||||
if (showLines) {
|
||||
Overlays.editOverlay(palmRay[side], {
|
||||
start: palmPoint,
|
||||
end: checkPoint,
|
||||
visible: showLines
|
||||
});
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
Overlays.editOverlay(fingerRays[side][fingerKeys[i]], {
|
||||
start: data.fingers[fingerKeys[i]],
|
||||
end: checkPoint,
|
||||
visible: showLines
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (showSphere) {
|
||||
Overlays.editOverlay(sphereHand[side], {
|
||||
position: spherePos,
|
||||
scale: {
|
||||
x: 2*dist,
|
||||
y: 2*dist,
|
||||
z: 2*dist
|
||||
},
|
||||
visible: showSphere
|
||||
});
|
||||
}
|
||||
|
||||
// Update the intersection of only one finger at a time
|
||||
var finger = fingerKeys[updateFingerWithIndex];
|
||||
var nearbyEntities = Entities.findEntities(spherePos, dist);
|
||||
// Filter the entities that are allowed to be touched
|
||||
var touchableEntities = nearbyEntities.filter(function (id) {
|
||||
return untouchableEntities.indexOf(id) == -1;
|
||||
});
|
||||
var intersection;
|
||||
if (rayPicks[side][finger] !== undefined) {
|
||||
intersection = RayPick.getPrevRayPickResult(rayPicks[side][finger]);
|
||||
}
|
||||
|
||||
var animationSteps = defaultAnimationSteps;
|
||||
var newFingerData = dataDefault[side][finger];
|
||||
var isAbleToGrab = false;
|
||||
if (touchableEntities.length > 0) {
|
||||
RayPick.setIncludeItems(rayPicks[side][finger], touchableEntities);
|
||||
|
||||
if (intersection === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var percent = 0; // Initialize
|
||||
isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist;
|
||||
if (isAbleToGrab && !getTouching(side)) {
|
||||
acquireDefaultPose(side); // take a snapshot of the default pose before touch starts
|
||||
newFingerData = dataDefault[side][finger]; // assign default pose to finger data
|
||||
}
|
||||
// Store if this finger is touching something
|
||||
isTouching[side][finger] = isAbleToGrab;
|
||||
if (isAbleToGrab) {
|
||||
// update the open/close percentage for this finger
|
||||
var FINGER_REACT_MULTIPLIER = 2.8;
|
||||
|
||||
percent = intersection.distance/(FINGER_REACT_MULTIPLIER*dist);
|
||||
|
||||
var THUMB_FACTOR = 0.2;
|
||||
var FINGER_FACTOR = 0.05;
|
||||
|
||||
// Amount of grab coefficient added to the fingers - thumb is higher
|
||||
var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR;
|
||||
percent += grabMultiplier * grabPercent[side];
|
||||
|
||||
// Calculate new interpolation data
|
||||
var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1);
|
||||
// Assign close/open ratio to finger to simulate touch
|
||||
newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1);
|
||||
animationSteps = touchAnimationSteps;
|
||||
}
|
||||
varsToDebug.fingerPercent[side][finger] = percent;
|
||||
|
||||
}
|
||||
if (!isAbleToGrab) {
|
||||
dataFailed[side][finger] = dataFailed[side][finger] === 0 ? 1 : 2;
|
||||
} else {
|
||||
dataFailed[side][finger] = 0;
|
||||
}
|
||||
// If it only fails once it will not update increments
|
||||
if (dataFailed[side][finger] !== 1) {
|
||||
// Calculate animation increments
|
||||
dataDelta[side][finger] =
|
||||
multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps);
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate the finger joint names
|
||||
function getJointNames(side, finger, count) {
|
||||
var names = [];
|
||||
for (var i = 1; i < count+1; i++) {
|
||||
var name = side[0].toUpperCase()+side.substring(1)+"Hand"+finger[0].toUpperCase()+finger.substring(1)+(i);
|
||||
names.push(name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
// Capture the controller values
|
||||
var leftTriggerPress = function (value) {
|
||||
varsToDebug.triggerValues.leftTriggerValue = value;
|
||||
// the value for the trigger increments the hand-close percentage
|
||||
grabPercent.left = value;
|
||||
};
|
||||
|
||||
var leftTriggerClick = function (value) {
|
||||
varsToDebug.triggerValues.leftTriggerClicked = value;
|
||||
};
|
||||
|
||||
var rightTriggerPress = function (value) {
|
||||
varsToDebug.triggerValues.rightTriggerValue = value;
|
||||
// the value for the trigger increments the hand-close percentage
|
||||
grabPercent.right = value;
|
||||
};
|
||||
|
||||
var rightTriggerClick = function (value) {
|
||||
varsToDebug.triggerValues.rightTriggerClicked = value;
|
||||
};
|
||||
|
||||
var leftSecondaryPress = function (value) {
|
||||
varsToDebug.triggerValues.leftSecondaryValue = value;
|
||||
};
|
||||
|
||||
var rightSecondaryPress = function (value) {
|
||||
varsToDebug.triggerValues.rightSecondaryValue = value;
|
||||
};
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.handTouch";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
mapping.from([Controller.Standard.RT]).peek().to(rightTriggerPress);
|
||||
mapping.from([Controller.Standard.RTClick]).peek().to(rightTriggerClick);
|
||||
mapping.from([Controller.Standard.LT]).peek().to(leftTriggerPress);
|
||||
mapping.from([Controller.Standard.LTClick]).peek().to(leftTriggerClick);
|
||||
|
||||
mapping.from([Controller.Standard.RB]).peek().to(rightSecondaryPress);
|
||||
mapping.from([Controller.Standard.LB]).peek().to(leftSecondaryPress);
|
||||
mapping.from([Controller.Standard.LeftGrip]).peek().to(leftSecondaryPress);
|
||||
mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress);
|
||||
|
||||
Controller.enableMapping(MAPPING_NAME);
|
||||
|
||||
if (showLines && !linesCreated) {
|
||||
createDebugLines();
|
||||
linesCreated = true;
|
||||
}
|
||||
|
||||
if (showSphere && !sphereCreated) {
|
||||
createDebugSphere();
|
||||
sphereCreated = true;
|
||||
}
|
||||
|
||||
function getTouching(side) {
|
||||
var animating = false;
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
animating = animating || isTouching[side][finger];
|
||||
}
|
||||
return animating; // return false only if none of the fingers are touching
|
||||
}
|
||||
|
||||
function reEstimatePalmData() {
|
||||
["right", "left"].forEach(function(side) {
|
||||
estimatePalmData(side);
|
||||
});
|
||||
}
|
||||
|
||||
function recreateRayPicks() {
|
||||
["right", "left"].forEach(function(side) {
|
||||
createRayPicks(side);
|
||||
});
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
["right", "left"].forEach(function (side) {
|
||||
if (linesCreated) {
|
||||
Overlays.deleteOverlay(palmRay[side]);
|
||||
}
|
||||
if (sphereCreated) {
|
||||
Overlays.deleteOverlay(sphereHand[side]);
|
||||
}
|
||||
clearRayPicks(side);
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
var jointSuffixes = 3; // We need to clear the joints 0, 1 and 2 joints
|
||||
var names = getJointNames(side, finger, jointSuffixes);
|
||||
for (var j = 0; j < names.length; j++) {
|
||||
var index = MyAvatar.getJointIndex(names[j]);
|
||||
MyAvatar.clearJointData(index);
|
||||
}
|
||||
if (linesCreated) {
|
||||
Overlays.deleteOverlay(fingerRays[side][finger]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MyAvatar.shouldDisableHandTouchChanged.connect(function (shouldDisable) {
|
||||
if (shouldDisable) {
|
||||
if (handTouchEnabled) {
|
||||
cleanUp();
|
||||
}
|
||||
} else {
|
||||
if (!handTouchEnabled) {
|
||||
reEstimatePalmData();
|
||||
recreateRayPicks();
|
||||
}
|
||||
}
|
||||
handTouchEnabled = !shouldDisable;
|
||||
});
|
||||
|
||||
Controller.inputDeviceRunningChanged.connect(function (deviceName, isEnabled) {
|
||||
if (deviceName == LEAP_MOTION_NAME) {
|
||||
leapMotionEnabled = isEnabled;
|
||||
}
|
||||
});
|
||||
|
||||
MyAvatar.disableHandTouchForIDChanged.connect(function (entityID, disable) {
|
||||
var entityIndex = untouchableEntities.indexOf(entityID);
|
||||
if (disable) {
|
||||
if (entityIndex == -1) {
|
||||
untouchableEntities.push(entityID);
|
||||
}
|
||||
} else {
|
||||
if (entityIndex != -1) {
|
||||
untouchableEntities.splice(entityIndex, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MyAvatar.onLoadComplete.connect(function () {
|
||||
// Sometimes the rig is not ready when this signal is trigger
|
||||
console.log("avatar loaded");
|
||||
Script.setTimeout(function() {
|
||||
reEstimatePalmData();
|
||||
recreateRayPicks();
|
||||
}, MSECONDS_AFTER_LOAD);
|
||||
});
|
||||
|
||||
MyAvatar.sensorToWorldScaleChanged.connect(function() {
|
||||
reEstimatePalmData();
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
Script.update.connect(function () {
|
||||
|
||||
if (!handTouchEnabled || leapMotionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// index of the finger that needs to be updated this frame
|
||||
updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0;
|
||||
|
||||
["right", "left"].forEach(function(side) {
|
||||
|
||||
if (!palmData[side].set) {
|
||||
reEstimatePalmData();
|
||||
recreateRayPicks();
|
||||
}
|
||||
|
||||
// recalculate the base data
|
||||
updateSphereHand(side);
|
||||
activateNextRay(side, updateFingerWithIndex);
|
||||
|
||||
// this vars manage the transition to default pose
|
||||
var isHandTouching = getTouching(side);
|
||||
countToDefault[side] = isHandTouching ? 0 : countToDefault[side] + 1;
|
||||
|
||||
for (var i = 0; i < fingerKeys.length; i++) {
|
||||
var finger = fingerKeys[i];
|
||||
var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints
|
||||
var names = getJointNames(side, finger, jointSuffixes);
|
||||
|
||||
// Add the animation increments
|
||||
dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1);
|
||||
|
||||
// update every finger joint
|
||||
for (var j = 0; j < names.length; j++) {
|
||||
var index = MyAvatar.getJointIndex(names[j]);
|
||||
// if no finger is touching restate the default poses
|
||||
if (isHandTouching || (dataDefault[side].set &&
|
||||
countToDefault[side] < fingerKeys.length*touchAnimationSteps)) {
|
||||
var quatRot = dataCurrent[side][finger][j];
|
||||
MyAvatar.setJointRotation(index, quatRot);
|
||||
} else {
|
||||
MyAvatar.clearJointData(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}());
|
184
scripts/simplifiedUI/system/controllers/squeezeHands.js
Normal file
184
scripts/simplifiedUI/system/controllers/squeezeHands.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// controllers/squeezeHands.js
|
||||
//
|
||||
// Created by Anthony J. Thibault
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Default script to drive the animation of the hands based on hand controllers.
|
||||
//
|
||||
// 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, MyAvatar, Messages, Controller */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var lastLeftTrigger = 0;
|
||||
var lastRightTrigger = 0;
|
||||
var leftHandOverlayAlpha = 0;
|
||||
var rightHandOverlayAlpha = 0;
|
||||
|
||||
// var CONTROLLER_DEAD_SPOT = 0.25;
|
||||
var TRIGGER_SMOOTH_TIMESCALE = 0.1;
|
||||
var OVERLAY_RAMP_RATE = 8.0;
|
||||
|
||||
var animStateHandlerID;
|
||||
|
||||
var leftIndexPointingOverride = 0;
|
||||
var rightIndexPointingOverride = 0;
|
||||
var leftThumbRaisedOverride = 0;
|
||||
var rightThumbRaisedOverride = 0;
|
||||
|
||||
var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
|
||||
|
||||
var isLeftIndexPointing = false;
|
||||
var isRightIndexPointing = false;
|
||||
var isLeftThumbRaised = false;
|
||||
var isRightThumbRaised = false;
|
||||
|
||||
function clamp(val, min, max) {
|
||||
return Math.min(Math.max(val, min), max);
|
||||
}
|
||||
|
||||
// function normalizeControllerValue(val) {
|
||||
// return clamp((val - CONTROLLER_DEAD_SPOT) / (1 - CONTROLLER_DEAD_SPOT), 0, 1);
|
||||
// }
|
||||
|
||||
function lerp(a, b, alpha) {
|
||||
return a * (1 - alpha) + b * alpha;
|
||||
}
|
||||
|
||||
function init() {
|
||||
Script.update.connect(update);
|
||||
animStateHandlerID = MyAvatar.addAnimationStateHandler(
|
||||
animStateHandler,
|
||||
[
|
||||
"leftHandOverlayAlpha", "leftHandGraspAlpha",
|
||||
"rightHandOverlayAlpha", "rightHandGraspAlpha",
|
||||
"isLeftHandGrasp", "isLeftIndexPoint", "isLeftThumbRaise", "isLeftIndexPointAndThumbRaise",
|
||||
"isRightHandGrasp", "isRightIndexPoint", "isRightThumbRaise", "isRightIndexPointAndThumbRaise"
|
||||
]
|
||||
);
|
||||
Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.connect(handleMessages);
|
||||
}
|
||||
|
||||
function animStateHandler(props) {
|
||||
return {
|
||||
leftHandOverlayAlpha: leftHandOverlayAlpha,
|
||||
leftHandGraspAlpha: lastLeftTrigger,
|
||||
rightHandOverlayAlpha: rightHandOverlayAlpha,
|
||||
rightHandGraspAlpha: lastRightTrigger,
|
||||
|
||||
isLeftHandGrasp: !isLeftIndexPointing && !isLeftThumbRaised,
|
||||
isLeftIndexPoint: isLeftIndexPointing && !isLeftThumbRaised,
|
||||
isLeftThumbRaise: !isLeftIndexPointing && isLeftThumbRaised,
|
||||
isLeftIndexPointAndThumbRaise: isLeftIndexPointing && isLeftThumbRaised,
|
||||
|
||||
isRightHandGrasp: !isRightIndexPointing && !isRightThumbRaised,
|
||||
isRightIndexPoint: isRightIndexPointing && !isRightThumbRaised,
|
||||
isRightThumbRaise: !isRightIndexPointing && isRightThumbRaised,
|
||||
isRightIndexPointAndThumbRaise: isRightIndexPointing && isRightThumbRaised
|
||||
};
|
||||
}
|
||||
|
||||
function update(dt) {
|
||||
var leftTrigger = clamp(Controller.getValue(Controller.Standard.LT) + Controller.getValue(Controller.Standard.LeftGrip), 0, 1);
|
||||
var rightTrigger = clamp(Controller.getValue(Controller.Standard.RT) + Controller.getValue(Controller.Standard.RightGrip), 0, 1);
|
||||
|
||||
// Average last few trigger values together for a bit of smoothing
|
||||
var tau = clamp(dt / TRIGGER_SMOOTH_TIMESCALE, 0, 1);
|
||||
lastLeftTrigger = lerp(leftTrigger, lastLeftTrigger, tau);
|
||||
lastRightTrigger = lerp(rightTrigger, lastRightTrigger, tau);
|
||||
|
||||
// ramp on/off left hand overlay
|
||||
var leftHandPose = Controller.getPoseValue(Controller.Standard.LeftHand);
|
||||
if (leftHandPose.valid) {
|
||||
leftHandOverlayAlpha = clamp(leftHandOverlayAlpha + OVERLAY_RAMP_RATE * dt, 0, 1);
|
||||
} else {
|
||||
leftHandOverlayAlpha = clamp(leftHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
|
||||
}
|
||||
|
||||
// ramp on/off right hand overlay
|
||||
var rightHandPose = Controller.getPoseValue(Controller.Standard.RightHand);
|
||||
if (rightHandPose.valid) {
|
||||
rightHandOverlayAlpha = clamp(rightHandOverlayAlpha + OVERLAY_RAMP_RATE * dt, 0, 1);
|
||||
} else {
|
||||
rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
|
||||
}
|
||||
|
||||
// Pointing index fingers and raising thumbs
|
||||
isLeftIndexPointing = (leftIndexPointingOverride > 0) || (leftHandPose.valid && Controller.getValue(Controller.Standard.LeftIndexPoint) === 1);
|
||||
isRightIndexPointing = (rightIndexPointingOverride > 0) || (rightHandPose.valid && Controller.getValue(Controller.Standard.RightIndexPoint) === 1);
|
||||
isLeftThumbRaised = (leftThumbRaisedOverride > 0) || (leftHandPose.valid && Controller.getValue(Controller.Standard.LeftThumbUp) === 1);
|
||||
isRightThumbRaised = (rightThumbRaisedOverride > 0) || (rightHandPose.valid && Controller.getValue(Controller.Standard.RightThumbUp) === 1);
|
||||
}
|
||||
|
||||
function handleMessages(channel, message, sender) {
|
||||
if (sender === MyAvatar.sessionUUID && channel === HIFI_POINT_INDEX_MESSAGE_CHANNEL) {
|
||||
var data = JSON.parse(message);
|
||||
|
||||
if (data.pointIndex !== undefined) {
|
||||
if (data.pointIndex) {
|
||||
leftIndexPointingOverride++;
|
||||
rightIndexPointingOverride++;
|
||||
} else {
|
||||
leftIndexPointingOverride--;
|
||||
rightIndexPointingOverride--;
|
||||
}
|
||||
}
|
||||
if (data.pointLeftIndex !== undefined) {
|
||||
if (data.pointLeftIndex) {
|
||||
leftIndexPointingOverride++;
|
||||
} else {
|
||||
leftIndexPointingOverride--;
|
||||
}
|
||||
}
|
||||
if (data.pointRightIndex !== undefined) {
|
||||
if (data.pointRightIndex) {
|
||||
rightIndexPointingOverride++;
|
||||
} else {
|
||||
rightIndexPointingOverride--;
|
||||
}
|
||||
}
|
||||
if (data.raiseThumbs !== undefined) {
|
||||
if (data.raiseThumbs) {
|
||||
leftThumbRaisedOverride++;
|
||||
rightThumbRaisedOverride++;
|
||||
} else {
|
||||
leftThumbRaisedOverride--;
|
||||
rightThumbRaisedOverride--;
|
||||
}
|
||||
}
|
||||
if (data.raiseLeftThumb !== undefined) {
|
||||
if (data.raiseLeftThumb) {
|
||||
leftThumbRaisedOverride++;
|
||||
} else {
|
||||
leftThumbRaisedOverride--;
|
||||
}
|
||||
}
|
||||
if (data.raiseRightThumb !== undefined) {
|
||||
if (data.raiseRightThumb) {
|
||||
rightThumbRaisedOverride++;
|
||||
} else {
|
||||
rightThumbRaisedOverride--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
Script.update.disconnect(update);
|
||||
MyAvatar.removeAnimationStateHandler(animStateHandlerID);
|
||||
Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.disconnect(handleMessages);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(shutdown);
|
||||
|
||||
init();
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -0,0 +1,174 @@
|
|||
"use strict";
|
||||
|
||||
// Created by james b. pollack @imgntn on 8/18/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// advanced movements settings are in individual controller json files
|
||||
// what we do is check the status of the 'advance movement' checkbox when you enter HMD mode
|
||||
// if 'advanced movement' is checked...we give you the defaults that are in the json.
|
||||
// if 'advanced movement' is not checked... we override the advanced controls with basic ones.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* jslint bitwise: true */
|
||||
|
||||
/* global Script, Quat, MyAvatar, HMD, Controller, Messages*/
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var TWO_SECONDS_INTERVAL = 2000;
|
||||
var FLYING_MAPPING_NAME = 'Hifi-Flying-Dev-' + Math.random();
|
||||
var DRIVING_MAPPING_NAME = 'Hifi-Driving-Dev-' + Math.random();
|
||||
|
||||
var flyingMapping = null;
|
||||
var drivingMapping = null;
|
||||
|
||||
var TURN_RATE = 1000;
|
||||
var isDisabled = false;
|
||||
|
||||
var previousFlyingState = MyAvatar.getFlyingEnabled();
|
||||
var previousDrivingState = false;
|
||||
|
||||
function rotate180() {
|
||||
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.angleAxis(180, {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
}));
|
||||
MyAvatar.orientation = newOrientation;
|
||||
}
|
||||
|
||||
var inFlipTurn = false;
|
||||
|
||||
function registerBasicMapping() {
|
||||
|
||||
drivingMapping = Controller.newMapping(DRIVING_MAPPING_NAME);
|
||||
drivingMapping.from(Controller.Standard.LY).to(function(value) {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 1 && Controller.Hardware.OculusTouch !== undefined) {
|
||||
rotate180();
|
||||
} else if (Controller.Hardware.Vive !== undefined) {
|
||||
if (value > 0.75 && inFlipTurn === false) {
|
||||
inFlipTurn = true;
|
||||
rotate180();
|
||||
Script.setTimeout(function() {
|
||||
inFlipTurn = false;
|
||||
}, TURN_RATE);
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
flyingMapping = Controller.newMapping(FLYING_MAPPING_NAME);
|
||||
flyingMapping.from(Controller.Standard.RY).to(function(value) {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 1 && Controller.Hardware.OculusTouch !== undefined) {
|
||||
rotate180();
|
||||
} else if (Controller.Hardware.Vive !== undefined) {
|
||||
if (value > 0.75 && inFlipTurn === false) {
|
||||
inFlipTurn = true;
|
||||
rotate180();
|
||||
Script.setTimeout(function() {
|
||||
inFlipTurn = false;
|
||||
}, TURN_RATE);
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
Controller.disableMapping(FLYING_MAPPING_NAME);
|
||||
Controller.disableMapping(DRIVING_MAPPING_NAME);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
registerBasicMapping();
|
||||
|
||||
Script.setTimeout(function() {
|
||||
if (MyAvatar.useAdvanceMovementControls) {
|
||||
Controller.disableMapping(DRIVING_MAPPING_NAME);
|
||||
} else {
|
||||
Controller.enableMapping(DRIVING_MAPPING_NAME);
|
||||
}
|
||||
|
||||
if (MyAvatar.getFlyingEnabled()) {
|
||||
Controller.disableMapping(FLYING_MAPPING_NAME);
|
||||
} else {
|
||||
Controller.enableMapping(FLYING_MAPPING_NAME);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
|
||||
HMD.displayModeChanged.connect(function(isHMDMode) {
|
||||
if (isHMDMode) {
|
||||
if (Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) {
|
||||
if (MyAvatar.useAdvancedMovementControls) {
|
||||
Controller.disableMapping(DRIVING_MAPPING_NAME);
|
||||
} else {
|
||||
Controller.enableMapping(DRIVING_MAPPING_NAME);
|
||||
}
|
||||
|
||||
if (MyAvatar.getFlyingEnabled()) {
|
||||
Controller.disableMapping(FLYING_MAPPING_NAME);
|
||||
} else {
|
||||
Controller.enableMapping(FLYING_MAPPING_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function update() {
|
||||
if ((Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) && HMD.active) {
|
||||
var flying = MyAvatar.getFlyingEnabled();
|
||||
var driving = MyAvatar.useAdvancedMovementControls;
|
||||
|
||||
if (flying !== previousFlyingState) {
|
||||
if (flying) {
|
||||
Controller.disableMapping(FLYING_MAPPING_NAME);
|
||||
} else {
|
||||
Controller.enableMapping(FLYING_MAPPING_NAME);
|
||||
}
|
||||
|
||||
previousFlyingState = flying;
|
||||
}
|
||||
|
||||
if (driving !== previousDrivingState) {
|
||||
if (driving) {
|
||||
Controller.disableMapping(DRIVING_MAPPING_NAME);
|
||||
} else {
|
||||
Controller.enableMapping(DRIVING_MAPPING_NAME);
|
||||
}
|
||||
previousDrivingState = driving;
|
||||
}
|
||||
}
|
||||
Script.setTimeout(update, TWO_SECONDS_INTERVAL);
|
||||
}
|
||||
|
||||
Script.setTimeout(update, TWO_SECONDS_INTERVAL);
|
||||
|
||||
var HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL = 'Hifi-Advanced-Movement-Disabler';
|
||||
function handleMessage(channel, message, sender) {
|
||||
if (channel === HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL) {
|
||||
if (message === 'disable') {
|
||||
isDisabled = true;
|
||||
} else if (message === 'enable') {
|
||||
isDisabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.subscribe(HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL);
|
||||
Messages.messageReceived.connect(handleMessage);
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -0,0 +1,372 @@
|
|||
|
||||
//
|
||||
// touchControllerConfiguration.js
|
||||
//
|
||||
// Created by Ryan Huffman on 12/06/16
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* globals TOUCH_CONTROLLER_CONFIGURATION_LEFT:true, TOUCH_CONTROLLER_CONFIGURATION_RIGHT:true,
|
||||
Quat, Vec3, Script, MyAvatar, Controller */
|
||||
/* eslint camelcase: ["error", { "properties": "never" }] */
|
||||
|
||||
var leftBaseRotation = Quat.multiply(
|
||||
Quat.fromPitchYawRollDegrees(-90, 0, 0),
|
||||
Quat.fromPitchYawRollDegrees(0, 0, 90)
|
||||
);
|
||||
var rightBaseRotation = Quat.multiply(
|
||||
Quat.fromPitchYawRollDegrees(-90, 0, 0),
|
||||
Quat.fromPitchYawRollDegrees(0, 0, -90)
|
||||
);
|
||||
|
||||
// keep these in sync with the values from OculusHelpers.cpp
|
||||
var CONTROLLER_LENGTH_OFFSET = 0.0762;
|
||||
// var CONTROLLER_LATERAL_OFFSET = 0.0381;
|
||||
// var CONTROLLER_VERTICAL_OFFSET = 0.0381;
|
||||
// var CONTROLLER_FORWARD_OFFSET = 0.1524;
|
||||
|
||||
var leftBasePosition = Vec3.multiplyQbyV(leftBaseRotation, {
|
||||
x: -CONTROLLER_LENGTH_OFFSET / 2.0,
|
||||
y: CONTROLLER_LENGTH_OFFSET / 2.0,
|
||||
z: CONTROLLER_LENGTH_OFFSET * 1.5
|
||||
});
|
||||
var rightBasePosition = Vec3.multiplyQbyV(rightBaseRotation, {
|
||||
x: CONTROLLER_LENGTH_OFFSET / 2.0,
|
||||
y: CONTROLLER_LENGTH_OFFSET / 2.0,
|
||||
z: CONTROLLER_LENGTH_OFFSET * 1.5
|
||||
});
|
||||
|
||||
var BASE_URL = Script.resourcesPath() + "meshes/controller/touch/";
|
||||
|
||||
TOUCH_CONTROLLER_CONFIGURATION_LEFT = {
|
||||
name: "Touch",
|
||||
controllers: [
|
||||
{
|
||||
modelURL: BASE_URL + "touch_l_body.fbx",
|
||||
jointIndex: MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"),
|
||||
naturalPosition: { x: 0.01648625358939171, y: -0.03551870584487915, z: -0.018527675420045853 },
|
||||
dimensions: { x: 0.11053799837827682, y: 0.0995776429772377, z: 0.10139888525009155 },
|
||||
rotation: leftBaseRotation,
|
||||
position: leftBasePosition,
|
||||
|
||||
parts: {
|
||||
tips: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "Oculus-Labels-L.fbx",
|
||||
naturalPosition: { x: -0.022335469722747803, y: 0.00022516027092933655, z: 0.020340695977211 },
|
||||
naturalDimensions: { x: 0.132063, y: 0.0856, z: 0.130282 },
|
||||
|
||||
textureName: "blank",
|
||||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Trigger.png"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Rotate.png"
|
||||
},
|
||||
grip: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Grip-oculus.png"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Teleport.png"
|
||||
},
|
||||
both_triggers: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Grip-Trigger.png"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: "rotational",
|
||||
modelURL: BASE_URL + "touch_l_trigger.fbx",
|
||||
naturalPosition: { x: 0.0008544912561774254, y: -0.019867943599820137, z: 0.018800459802150726 },
|
||||
naturalDimensions: { x: 0.027509, y: 0.025211, z: 0.018443 },
|
||||
|
||||
// rotational
|
||||
input: Controller.Standard.LT,
|
||||
origin: { x: 0, y: -0.015, z: -0.00 },
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: 1, y: 0, z: 0 },
|
||||
maxAngle: 17,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_trigger.fbx/touch_l_trigger.fbm/L_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_trigger.fbx/touch_l_trigger.fbm/L_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
grip: {
|
||||
type: "linear",
|
||||
modelURL: BASE_URL + "touch_l_bumper.fbx",
|
||||
naturalPosition: { x: 0.00008066371083259583, y: -0.02715788595378399, z: -0.02448512241244316 },
|
||||
naturalDimensions: { x: 0.017444, y: 0.020297, z: 0.026003 },
|
||||
|
||||
// linear properties
|
||||
// Offset from origin = 0.36470, 0.11048, 0.11066
|
||||
input: "OculusTouch.LeftGrip",
|
||||
axis: { x: 1, y: 0.302933918, z: 0.302933918 },
|
||||
maxTranslation: 0.003967,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_bumper.fbx/touch_l_bumper.fbm/L_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_bumper.fbx/touch_l_bumper.fbm/L_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
joystick: {
|
||||
type: "joystick",
|
||||
modelURL: BASE_URL + "touch_l_joystick.fbx",
|
||||
naturalPosition: { x: 0.0075613949447870255, y: -0.008225866593420506, z: 0.004792703315615654 },
|
||||
naturalDimensions: { x: 0.027386, y: 0.033254, z: 0.027272 },
|
||||
|
||||
// joystick
|
||||
xInput: "OculusTouch.LX",
|
||||
yInput: "OculusTouch.LY",
|
||||
originOffset: { x: 0, y: -0.0028564, z: -0.00 },
|
||||
xHalfAngle: 20,
|
||||
yHalfAngle: 20,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_joystick.fbx/touch_l_joystick.fbm/L_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_joystick.fbx/touch_l_joystick.fbm/L_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
button_a: {
|
||||
type: "linear",
|
||||
modelURL: BASE_URL + "touch_l_button_x.fbx",
|
||||
naturalPosition: { x: -0.009307309985160828, y: -0.00005015172064304352, z: -0.012594521045684814 },
|
||||
naturalDimensions: { x: 0.009861, y: 0.004345, z: 0.00982 },
|
||||
|
||||
input: "OculusTouch.X",
|
||||
axis: { x: 0, y: -1, z: 0 },
|
||||
maxTranslation: 0.001,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_button_x.fbx/touch_l_button_x.fbm/L_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_button_x.fbx/touch_l_button_x.fbm/L_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
button_b: {
|
||||
type: "linear",
|
||||
modelURL: BASE_URL + "touch_l_button_y.fbx",
|
||||
naturalPosition: { x: -0.01616849936544895, y: -0.000050364527851343155, z: 0.0017703399062156677 },
|
||||
naturalDimensions: { x: 0.010014, y: 0.004412, z: 0.009972 },
|
||||
|
||||
input: "OculusTouch.Y",
|
||||
axis: { x: 0, y: -1, z: 0 },
|
||||
maxTranslation: 0.001,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_button_y.fbx/touch_l_button_y.fbm/L_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_l_button_y.fbx/touch_l_button_y.fbm/L_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
TOUCH_CONTROLLER_CONFIGURATION_RIGHT = {
|
||||
name: "Touch",
|
||||
controllers: [
|
||||
{
|
||||
modelURL: BASE_URL + "touch_r_body.fbx",
|
||||
jointIndex: MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"),
|
||||
naturalPosition: { x: -0.016486231237649918, y: -0.03551865369081497, z: -0.018527653068304062 },
|
||||
dimensions: { x: 0.11053784191608429, y: 0.09957750141620636, z: 0.10139875113964081 },
|
||||
rotation: rightBaseRotation,
|
||||
position: rightBasePosition,
|
||||
|
||||
parts: {
|
||||
tips: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "Oculus-Labels-R.fbx",
|
||||
naturalPosition: { x: 0.009739525616168976, y: -0.0017818436026573181, z: 0.016794726252555847 },
|
||||
naturalDimensions: { x: 0.129049, y: 0.078297, z: 0.139492 },
|
||||
|
||||
textureName: "blank",
|
||||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Trigger.png"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Rotate.png"
|
||||
},
|
||||
grip: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Grip-oculus.png"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Teleport.png"
|
||||
},
|
||||
both_triggers: {
|
||||
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Grip-Trigger.png"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: "rotational",
|
||||
modelURL: BASE_URL + "touch_r_trigger.fbx",
|
||||
naturalPosition: { x: -0.0008544912561774254, y: -0.019867943599820137, z: 0.018800459802150726 },
|
||||
naturalDimensions: { x: 0.027384, y: 0.025201, z: 0.018425 },
|
||||
|
||||
// rotational
|
||||
input: "OculusTouch.RT",
|
||||
origin: { x: 0, y: -0.015, z: 0 },
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: 1, y: 0, z: 0 },
|
||||
maxAngle: 17,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_trigger.fbx/touch_r_trigger.fbm/R_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_trigger.fbx/touch_r_trigger.fbm/R_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
grip: {
|
||||
type: "linear",
|
||||
modelURL: BASE_URL + "touch_r_bumper.fbx",
|
||||
naturalPosition: { x: -0.0000806618481874466, y: -0.027157839387655258, z: -0.024485092610120773 },
|
||||
naturalDimensions: { x: 0.017268, y: 0.020366, z: 0.02599 },
|
||||
|
||||
// linear properties
|
||||
// Offset from origin = 0.36470, 0.11048, 0.11066
|
||||
input: "OculusTouch.RightGrip",
|
||||
axis: { x: -1, y: 0.302933918, z: 0.302933918 },
|
||||
maxTranslation: 0.003967,
|
||||
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_bumper.fbx/touch_r_bumper.fbm/R_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_bumper.fbx/touch_r_bumper.fbm/R_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
joystick: {
|
||||
type: "joystick",
|
||||
modelURL: BASE_URL + "touch_r_joystick.fbx",
|
||||
naturalPosition: { x: -0.007561382371932268, y: -0.008225853554904461, z: 0.00479268841445446 },
|
||||
naturalDimensions: { x: 0.027272, y: 0.033254, z: 0.027272 },
|
||||
|
||||
// joystick
|
||||
xInput: "OculusTouch.RX",
|
||||
yInput: "OculusTouch.RY",
|
||||
originOffset: { x: 0, y: -0.0028564, z: 0 },
|
||||
xHalfAngle: 20,
|
||||
yHalfAngle: 20,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_joystick.fbx/touch_r_joystick.fbm/R_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_joystick.fbx/touch_r_joystick.fbm/R_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
button_a: {
|
||||
type: "linear",
|
||||
modelURL: BASE_URL + "touch_r_button_a.fbx",
|
||||
naturalPosition: { x: 0.009307296946644783, y: -0.00005015172064304352, z: -0.012594504281878471 },
|
||||
naturalDimensions: { x: 0.00982, y: 0.004345, z: 0.00982 },
|
||||
|
||||
input: "OculusTouch.A",
|
||||
axis: { x: 0, y: -1, z: 0 },
|
||||
maxTranslation: 0.001,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_button_a.fbx/touch_r_button_a.fbm/R_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_button_a.fbx/touch_r_button_a.fbm/R_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
button_b: {
|
||||
type: "linear",
|
||||
modelURL: BASE_URL + "touch_r_button_b.fbx",
|
||||
naturalPosition: { x: 0.01616847701370716, y: -0.000050364527851343155, z: 0.0017703361809253693 },
|
||||
naturalDimensions: { x: 0.009972, y: 0.004412, z: 0.009972 },
|
||||
|
||||
input: "OculusTouch.B",
|
||||
axis: { x: 0, y: -1, z: 0 },
|
||||
maxTranslation: 0.001,
|
||||
|
||||
textureName: "tex-highlight",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_button_b.fbx/touch_r_button_b.fbm/R_controller_DIF.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + "touch_r_button_b.fbx/touch_r_button_b.fbm/R_controller-highlight_DIF.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
|
@ -0,0 +1,343 @@
|
|||
//
|
||||
// viveControllerConfiguration.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 10/20/16
|
||||
// Originally created by Ryan Huffman on 9/21/2016
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* globals VIVE_CONTROLLER_CONFIGURATION_LEFT:true, VIVE_CONTROLLER_CONFIGURATION_RIGHT:true,
|
||||
MyAvatar, Quat, Script, Vec3, Controller */
|
||||
/* eslint camelcase: ["error", { "properties": "never" }] */
|
||||
|
||||
// var LEFT_JOINT_INDEX = MyAvatar.getJointIndex("_CONTROLLER_LEFTHAND");
|
||||
// var RIGHT_JOINT_INDEX = MyAvatar.getJointIndex("_CONTROLLER_RIGHTHAND");
|
||||
|
||||
var leftBaseRotation = Quat.multiply(
|
||||
Quat.fromPitchYawRollDegrees(0, 0, 45),
|
||||
Quat.multiply(
|
||||
Quat.fromPitchYawRollDegrees(90, 0, 0),
|
||||
Quat.fromPitchYawRollDegrees(0, 0, 90)
|
||||
)
|
||||
);
|
||||
|
||||
var rightBaseRotation = Quat.multiply(
|
||||
Quat.fromPitchYawRollDegrees(0, 0, -45),
|
||||
Quat.multiply(
|
||||
Quat.fromPitchYawRollDegrees(90, 0, 0),
|
||||
Quat.fromPitchYawRollDegrees(0, 0, -90)
|
||||
)
|
||||
);
|
||||
|
||||
// keep these in sync with the values from plugins/openvr/src/OpenVrHelpers.cpp:303
|
||||
var CONTROLLER_LATERAL_OFFSET = 0.0381;
|
||||
var CONTROLLER_VERTICAL_OFFSET = 0.0495;
|
||||
var CONTROLLER_FORWARD_OFFSET = 0.1371;
|
||||
var leftBasePosition = {
|
||||
x: CONTROLLER_VERTICAL_OFFSET,
|
||||
y: CONTROLLER_FORWARD_OFFSET,
|
||||
z: CONTROLLER_LATERAL_OFFSET
|
||||
};
|
||||
var rightBasePosition = {
|
||||
x: -CONTROLLER_VERTICAL_OFFSET,
|
||||
y: CONTROLLER_FORWARD_OFFSET,
|
||||
z: CONTROLLER_LATERAL_OFFSET
|
||||
};
|
||||
|
||||
var viveNaturalDimensions = {
|
||||
x: 0.1174320001155138,
|
||||
y: 0.08361100335605443,
|
||||
z: 0.21942697931081057
|
||||
};
|
||||
|
||||
var viveNaturalPosition = {
|
||||
x: 0,
|
||||
y: -0.034076502197422087,
|
||||
z: 0.06380049744620919
|
||||
};
|
||||
|
||||
var BASE_URL = Script.resourcesPath();
|
||||
// var TIP_TEXTURE_BASE_URL = BASE_URL + "meshes/controller/vive_tips.fbm/";
|
||||
|
||||
var viveModelURL = BASE_URL + "meshes/controller/vive_body.fbx";
|
||||
// var viveTipsModelURL = BASE_URL + "meshes/controller/vive_tips.fbx";
|
||||
var viveTriggerModelURL = "meshes/controller/vive_trigger.fbx";
|
||||
|
||||
VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
||||
name: "Vive",
|
||||
controllers: [
|
||||
{
|
||||
modelURL: viveModelURL,
|
||||
jointIndex: MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"),
|
||||
naturalPosition: viveNaturalPosition,
|
||||
rotation: leftBaseRotation,
|
||||
position: Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 0, 45), leftBasePosition),
|
||||
|
||||
dimensions: viveNaturalDimensions,
|
||||
|
||||
parts: {
|
||||
// DISABLED FOR NOW
|
||||
/*
|
||||
tips: {
|
||||
type: "static",
|
||||
modelURL: viveTipsModelURL,
|
||||
naturalPosition: {"x":-0.004377640783786774,"y":-0.034371938556432724,"z":0.06769277155399323},
|
||||
naturalDimensions: {x: 0.191437, y: 0.094095, z: 0.085656},
|
||||
|
||||
textureName: "Tex.Blank",
|
||||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Trigger.png"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Rotate.png"
|
||||
},
|
||||
grip: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Grip.png"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Teleport.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
// The touchpad type draws a dot indicating the current touch/thumb position
|
||||
// and swaps in textures based on the thumb position.
|
||||
touchpad: {
|
||||
type: "touchpad",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trackpad.fbx",
|
||||
visibleInput: "Vive.RSTouch",
|
||||
xInput: "Vive.LX",
|
||||
yInput: "Vive.LY",
|
||||
naturalPosition: {"x":0,"y":0.000979491975158453,"z":0.04872849956154823},
|
||||
naturalDimensions: {x: 0.042824, y: 0.012537, z: 0.043115},
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
minPosition: { x: -0.035, y: 0.004, z: -0.005 },
|
||||
maxPosition: { x: -0.035, y: 0.004, z: -0.005 },
|
||||
disable_textureName: "Tex.touchpad-blank",
|
||||
|
||||
disable_defaultTextureLayer: "blank",
|
||||
disable_textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: "rotational",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trigger.fbx",
|
||||
input: Controller.Standard.LT,
|
||||
naturalPosition: {"x":0.000004500150680541992,"y":-0.027690507471561432,"z":0.04830199480056763},
|
||||
naturalDimensions: {x: 0.019105, y: 0.022189, z: 0.01909},
|
||||
origin: { x: 0, y: -0.015, z: -0.00 },
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: -1, y: 0, z: 0 },
|
||||
maxAngle: 25,
|
||||
|
||||
textureName: "Tex.black-trigger",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/black.jpg"
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/yellow.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
l_grip: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_l_grip.fbx",
|
||||
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
naturalDimensions: {x: 0.010094, y: 0.015064, z: 0.029552}
|
||||
},
|
||||
|
||||
r_grip: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_r_grip.fbx",
|
||||
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
naturalDimensions: {x: 0.010083, y: 0.015064, z: 0.029552}
|
||||
},
|
||||
|
||||
sys_button: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_sys_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311},
|
||||
naturalDimensions: {x: 0.009986, y: 0.004282, z: 0.010264}
|
||||
},
|
||||
|
||||
button: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564},
|
||||
naturalDimensions: {x: 0.009986, y: 0.004496, z: 0.010121}
|
||||
},
|
||||
button2: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564},
|
||||
naturalDimensions: {x: 0.009986, y: 0.004496, z: 0.010121}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
||||
name: "Vive Right",
|
||||
controllers: [
|
||||
{
|
||||
modelURL: viveModelURL,
|
||||
jointIndex: MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"),
|
||||
rotation: rightBaseRotation,
|
||||
position: Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 0, -45), rightBasePosition),
|
||||
|
||||
dimensions: viveNaturalDimensions,
|
||||
|
||||
naturalPosition: {
|
||||
x: 0,
|
||||
y: -0.034076502197422087,
|
||||
z: 0.06380049744620919
|
||||
},
|
||||
|
||||
parts: {
|
||||
// DISABLED FOR NOW
|
||||
/*
|
||||
tips: {
|
||||
type: "static",
|
||||
modelURL: viveTipsModelURL,
|
||||
naturalPosition: {"x":-0.004377640783786774,"y":-0.034371938556432724,"z":0.06769277155399323},
|
||||
naturalDimensions: {x: 0.191437, y: 0.094095, z: 0.085656},
|
||||
|
||||
textureName: "Tex.Blank",
|
||||
|
||||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Trigger.png"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Rotate.png"
|
||||
},
|
||||
grip: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Grip.png"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Teleport.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
// The touchpad type draws a dot indicating the current touch/thumb position
|
||||
// and swaps in textures based on the thumb position.
|
||||
touchpad: {
|
||||
type: "touchpad",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trackpad.fbx",
|
||||
visibleInput: "Vive.RSTouch",
|
||||
xInput: "Vive.RX",
|
||||
yInput: "Vive.RY",
|
||||
naturalPosition: { x: 0, y: 0.000979491975158453, z: 0.04872849956154823 },
|
||||
naturalDimensions: {x: 0.042824, y: 0.012537, z: 0.043115},
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
minPosition: { x: -0.035, y: 0.004, z: -0.005 },
|
||||
maxPosition: { x: -0.035, y: 0.004, z: -0.005 },
|
||||
disable_textureName: "Tex.touchpad-blank",
|
||||
|
||||
disable_defaultTextureLayer: "blank",
|
||||
disable_textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows-active.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: "rotational",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trigger.fbx",
|
||||
input: Controller.Standard.RT,
|
||||
naturalPosition: {"x":0.000004500150680541992,"y":-0.027690507471561432,"z":0.04830199480056763},
|
||||
naturalDimensions: {x: 0.019105, y: 0.022189, z: 0.01909},
|
||||
origin: { x: 0, y: -0.015, z: -0.00 },
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: -1, y: 0, z: 0 },
|
||||
maxAngle: 25,
|
||||
|
||||
textureName: "Tex.black-trigger",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/black.jpg"
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/yellow.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
l_grip: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_l_grip.fbx",
|
||||
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
naturalDimensions: {x: 0.010094, y: 0.015064, z: 0.029552}
|
||||
},
|
||||
|
||||
r_grip: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_r_grip.fbx",
|
||||
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
naturalDimensions: {x: 0.010083, y: 0.015064, z: 0.029552}
|
||||
},
|
||||
|
||||
sys_button: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_sys_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311},
|
||||
naturalDimensions: {x: 0.009986, y: 0.004282, z: 0.010264}
|
||||
},
|
||||
|
||||
button: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564},
|
||||
naturalDimensions: {x: 0.009986, y: 0.004496, z: 0.010121}
|
||||
},
|
||||
button2: {
|
||||
type: "static",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564},
|
||||
naturalDimensions: {x: 0.009986, y: 0.004496, z: 0.010121}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
387
scripts/simplifiedUI/system/progress.js
Normal file
387
scripts/simplifiedUI/system/progress.js
Normal file
|
@ -0,0 +1,387 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// progress.js
|
||||
// examples
|
||||
//
|
||||
// Created by David Rowe on 29 Jan 2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script displays a progress download indicator when downloads are in progress.
|
||||
//
|
||||
// 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
|
||||
function debug() {
|
||||
//print.apply(null, arguments);
|
||||
}
|
||||
|
||||
Script.include("/~/system/libraries/globals.js");
|
||||
var rawProgress = 100, // % raw value.
|
||||
displayProgress = 100, // % smoothed value to display.
|
||||
alpha = 0.0,
|
||||
alphaDelta = 0.0, // > 0 if fading in; < 0 if fading out.
|
||||
ALPHA_DELTA_IN = 0.15,
|
||||
ALPHA_DELTA_OUT = -0.02,
|
||||
fadeTimer = null,
|
||||
FADE_INTERVAL = 30, // ms between changes in alpha.
|
||||
fadeWaitTimer = null,
|
||||
FADE_OUT_WAIT = 1000, // Wait before starting to fade out after progress 100%.
|
||||
visible = false,
|
||||
|
||||
BAR_DESKTOP_2K_WIDTH = 2240, // Width of SVG image in pixels. Sized for 1920 x 1080 display with 6 visible repeats.
|
||||
BAR_DESKTOP_2K_REPEAT = 320, // Length of repeat in bar = 2240 / 7.
|
||||
BAR_DESKTOP_2K_HEIGHT = 3, // Display height of SVG
|
||||
BAR_DESKTOP_2K_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),
|
||||
|
||||
BAR_DESKTOP_4K_WIDTH = 4480, // Width of SVG image in pixels. Sized for 4096 x 1920 display with 6 visible repeats.
|
||||
BAR_DESKTOP_4K_REPEAT = 640, // Length of repeat in bar = 2240 / 7.
|
||||
BAR_DESKTOP_4K_HEIGHT = 6, // Display height of SVG
|
||||
BAR_DESKTOP_4K_URL = Script.resolvePath("assets/images/progress-bar-4k.svg"),
|
||||
|
||||
BAR_HMD_WIDTH = 2240, // Desktop image works with HMD well.
|
||||
BAR_HMD_REPEAT = 320,
|
||||
BAR_HMD_HEIGHT = 3,
|
||||
BAR_HMD_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),
|
||||
|
||||
BAR_Y_OFFSET_DESKTOP = 0, // Offset of progress bar while in desktop mode
|
||||
BAR_Y_OFFSET_HMD = -100, // Offset of progress bar while in HMD
|
||||
|
||||
ANIMATION_SECONDS_PER_REPEAT = 4, // Speed of bar animation
|
||||
|
||||
TEXT_HEIGHT = 32,
|
||||
TEXT_WIDTH = 256,
|
||||
TEXT_URL = Script.resolvePath("assets/images/progress-bar-text.svg"),
|
||||
windowWidth = 0,
|
||||
windowHeight = 0,
|
||||
barDesktop = {},
|
||||
barHMD = {},
|
||||
textDesktop = {}, // Separate desktop and HMD overlays because can't change text size after overlay created.
|
||||
textHMD = {},
|
||||
SCALE_TEXT_DESKTOP = 0.6,
|
||||
SCALE_TEXT_HMD = 1.0,
|
||||
isHMD = false,
|
||||
|
||||
// Max seen since downloads started. This is reset when all downloads have completed.
|
||||
maxSeen = 0,
|
||||
|
||||
// Progress is defined as: (pending_downloads + active_downloads) / max_seen
|
||||
// We keep track of both the current progress (rawProgress) and the
|
||||
// best progress we've seen (bestRawProgress). As you are downloading, you may
|
||||
// encounter new assets that require downloads, increasing the number of
|
||||
// pending downloads and thus decreasing your overall progress.
|
||||
bestRawProgress = 0,
|
||||
|
||||
// True if we have known active downloads
|
||||
isDownloading = false,
|
||||
|
||||
// Entities are streamed to users, so you don't receive them all at once; instead, you
|
||||
// receive them over a period of time. In many cases we end up in a situation where
|
||||
//
|
||||
// The initial delay cooldown keeps us from tracking progress before the allotted time
|
||||
// has passed.
|
||||
INITIAL_DELAY_COOLDOWN_TIME = 1000,
|
||||
initialDelayCooldown = 0,
|
||||
|
||||
isInInterstitialMode = false;
|
||||
|
||||
function fade() {
|
||||
|
||||
alpha = alpha + alphaDelta;
|
||||
|
||||
if (alpha < 0) {
|
||||
alpha = 0;
|
||||
} else if (alpha > 1) {
|
||||
alpha = 1;
|
||||
}
|
||||
|
||||
if (alpha === 0 || alpha === 1) { // Finished fading in or out
|
||||
alphaDelta = 0;
|
||||
Script.clearInterval(fadeTimer);
|
||||
}
|
||||
|
||||
if (alpha === 0) { // Finished fading out
|
||||
visible = false;
|
||||
}
|
||||
|
||||
Overlays.editOverlay(barDesktop.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible && !isHMD
|
||||
});
|
||||
Overlays.editOverlay(barHMD.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible && isHMD
|
||||
});
|
||||
Overlays.editOverlay(textDesktop.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible && !isHMD
|
||||
});
|
||||
Overlays.editOverlay(textHMD.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible && isHMD
|
||||
});
|
||||
}
|
||||
|
||||
Window.domainChanged.connect(function () {
|
||||
isDownloading = false;
|
||||
bestRawProgress = 100;
|
||||
rawProgress = 100;
|
||||
displayProgress = 100;
|
||||
});
|
||||
|
||||
function onDownloadInfoChanged(info) {
|
||||
|
||||
debug("PROGRESS: Download info changed ", info.downloading.length, info.pending, maxSeen);
|
||||
|
||||
// Update raw progress value
|
||||
if (info.downloading.length + info.pending === 0) {
|
||||
isDownloading = false;
|
||||
rawProgress = 100;
|
||||
bestRawProgress = 100;
|
||||
initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME;
|
||||
} else {
|
||||
var count = info.downloading.length + info.pending;
|
||||
if (!isDownloading) {
|
||||
isDownloading = true;
|
||||
bestRawProgress = 0;
|
||||
rawProgress = 0;
|
||||
initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME;
|
||||
displayProgress = 0;
|
||||
maxSeen = count;
|
||||
}
|
||||
if (count > maxSeen) {
|
||||
maxSeen = count;
|
||||
}
|
||||
if (initialDelayCooldown <= 0) {
|
||||
rawProgress = ((maxSeen - count) / maxSeen) * 100;
|
||||
|
||||
if (rawProgress > bestRawProgress) {
|
||||
bestRawProgress = rawProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug("PROGRESS:", rawProgress, bestRawProgress, maxSeen);
|
||||
}
|
||||
|
||||
function createOverlays() {
|
||||
barDesktop.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: barDesktop.url,
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: barDesktop.width - barDesktop.repeat,
|
||||
height: barDesktop.height
|
||||
},
|
||||
width: barDesktop.width,
|
||||
height: barDesktop.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
barHMD.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BAR_HMD_URL,
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: BAR_HMD_WIDTH - BAR_HMD_REPEAT,
|
||||
height: BAR_HMD_HEIGHT
|
||||
},
|
||||
width: barHMD.width,
|
||||
height: barHMD.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
textDesktop.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: TEXT_URL,
|
||||
width: textDesktop.width,
|
||||
height: textDesktop.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
textHMD.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: TEXT_URL,
|
||||
width: textHMD.width,
|
||||
height: textHMD.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
}
|
||||
|
||||
function deleteOverlays() {
|
||||
Overlays.deleteOverlay(barDesktop.overlay);
|
||||
Overlays.deleteOverlay(barHMD.overlay);
|
||||
Overlays.deleteOverlay(textDesktop.overlay);
|
||||
Overlays.deleteOverlay(textHMD.overlay);
|
||||
}
|
||||
|
||||
function updateProgressBarLocation() {
|
||||
var viewport = Controller.getViewportDimensions();
|
||||
|
||||
windowWidth = viewport.x;
|
||||
windowHeight = viewport.y;
|
||||
isHMD = HMD.active;
|
||||
|
||||
if (isHMD) {
|
||||
|
||||
Overlays.editOverlay(barHMD.overlay, {
|
||||
x: windowWidth / 2 - barHMD.width / 2,
|
||||
y: windowHeight - 2 * barHMD.height + BAR_Y_OFFSET_HMD
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textHMD.overlay, {
|
||||
x: windowWidth / 2 - textHMD.width / 2,
|
||||
y: windowHeight - 2 * barHMD.height - textHMD.height + BAR_Y_OFFSET_HMD
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
Overlays.editOverlay(barDesktop.overlay, {
|
||||
x: windowWidth / 2 - barDesktop.width / 2,
|
||||
y: windowHeight - 2 * barDesktop.height + BAR_Y_OFFSET_DESKTOP,
|
||||
width: barDesktop.width
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textDesktop.overlay, {
|
||||
x: windowWidth / 2 - textDesktop.width / 2,
|
||||
y: windowHeight - 2 * barDesktop.height - textDesktop.height + BAR_Y_OFFSET_DESKTOP
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var viewport, diff, x, gpuTextures;
|
||||
|
||||
initialDelayCooldown -= 30;
|
||||
|
||||
if (displayProgress < rawProgress) {
|
||||
diff = rawProgress - displayProgress;
|
||||
if (diff < 0.5) {
|
||||
displayProgress = rawProgress;
|
||||
} else {
|
||||
displayProgress += diff * 0.05;
|
||||
}
|
||||
}
|
||||
|
||||
gpuTextures = Render.getConfig("Stats").texturePendingGPUTransferCount;
|
||||
|
||||
// Update state
|
||||
if (!visible) { // Not visible because no recent downloads
|
||||
if ((displayProgress < 100 || gpuTextures > 0) && !isInInterstitialMode && !isInterstitialOverlaysVisible) { // Have started downloading so fade in
|
||||
visible = true;
|
||||
alphaDelta = ALPHA_DELTA_IN;
|
||||
fadeTimer = Script.setInterval(fade, FADE_INTERVAL);
|
||||
}
|
||||
} else if (alphaDelta !== 0.0) { // Fading in or out
|
||||
if (alphaDelta > 0) {
|
||||
if (rawProgress === 100 && gpuTextures === 0) { // Was downloading but now have finished so fade out
|
||||
alphaDelta = ALPHA_DELTA_OUT;
|
||||
}
|
||||
} else {
|
||||
if (displayProgress < 100 || gpuTextures > 0) { // Was finished downloading but have resumed so fade in
|
||||
alphaDelta = ALPHA_DELTA_IN;
|
||||
}
|
||||
}
|
||||
} else { // Fully visible because downloading or recently so
|
||||
if (fadeWaitTimer === null) {
|
||||
if (rawProgress === 100 && gpuTextures === 0) { // Was downloading but have finished so fade out soon
|
||||
fadeWaitTimer = Script.setTimeout(function () {
|
||||
alphaDelta = ALPHA_DELTA_OUT;
|
||||
fadeTimer = Script.setInterval(fade, FADE_INTERVAL);
|
||||
fadeWaitTimer = null;
|
||||
}, FADE_OUT_WAIT);
|
||||
}
|
||||
} else {
|
||||
if (displayProgress < 100 || gpuTextures > 0) { // Was finished and waiting to fade out but have resumed so
|
||||
// don't fade out
|
||||
Script.clearInterval(fadeWaitTimer);
|
||||
fadeWaitTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
x = ((Date.now() / 1000) % ANIMATION_SECONDS_PER_REPEAT) / ANIMATION_SECONDS_PER_REPEAT;
|
||||
if (!isHMD) {
|
||||
x = x * barDesktop.repeat;
|
||||
} else {
|
||||
x = x * BAR_HMD_REPEAT;
|
||||
}
|
||||
if (isInInterstitialMode || isInterstitialOverlaysVisible) {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
Overlays.editOverlay(barDesktop.overlay, {
|
||||
visible: !isHMD && visible,
|
||||
bounds: {
|
||||
x: barDesktop.repeat - x,
|
||||
y: windowHeight - barDesktop.height,
|
||||
width: barDesktop.width - barDesktop.repeat,
|
||||
height: barDesktop.height
|
||||
}
|
||||
});
|
||||
|
||||
Overlays.editOverlay(barHMD.overlay, {
|
||||
visible: isHMD && visible,
|
||||
bounds: {
|
||||
x: BAR_HMD_REPEAT - x,
|
||||
y: windowHeight - BAR_HMD_HEIGHT,
|
||||
width: BAR_HMD_WIDTH - BAR_HMD_REPEAT,
|
||||
height: BAR_HMD_HEIGHT
|
||||
}
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textDesktop.overlay, {
|
||||
visible: !isHMD && visible
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textHMD.overlay, {
|
||||
visible: isHMD && visible
|
||||
});
|
||||
|
||||
// Update 2D overlays to maintain positions at bottom middle of window
|
||||
viewport = Controller.getViewportDimensions();
|
||||
|
||||
if (viewport.x !== windowWidth || viewport.y !== windowHeight || isHMD !== HMD.active) {
|
||||
updateProgressBarLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function interstitialModeChanged(inMode) {
|
||||
isInInterstitialMode = inMode;
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
var is4k = Window.innerWidth > 3000;
|
||||
|
||||
isHMD = HMD.active;
|
||||
|
||||
barDesktop.width = is4k ? BAR_DESKTOP_4K_WIDTH - BAR_DESKTOP_4K_REPEAT : BAR_DESKTOP_2K_WIDTH - BAR_DESKTOP_2K_REPEAT;
|
||||
barDesktop.height = is4k ? BAR_DESKTOP_4K_HEIGHT : BAR_DESKTOP_2K_HEIGHT;
|
||||
barDesktop.repeat = is4k ? BAR_DESKTOP_4K_REPEAT : BAR_DESKTOP_2K_REPEAT;
|
||||
barDesktop.url = is4k ? BAR_DESKTOP_4K_URL : BAR_DESKTOP_2K_URL;
|
||||
barHMD.width = BAR_HMD_WIDTH - BAR_HMD_REPEAT;
|
||||
barHMD.height = BAR_HMD_HEIGHT;
|
||||
|
||||
textDesktop.width = SCALE_TEXT_DESKTOP * TEXT_WIDTH;
|
||||
textDesktop.height = SCALE_TEXT_DESKTOP * TEXT_HEIGHT;
|
||||
textHMD.width = SCALE_TEXT_HMD * TEXT_WIDTH;
|
||||
textHMD.height = SCALE_TEXT_HMD * TEXT_HEIGHT;
|
||||
|
||||
createOverlays();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
deleteOverlays();
|
||||
}
|
||||
|
||||
setUp();
|
||||
Window.interstitialModeChanged.connect(interstitialModeChanged);
|
||||
GlobalServices.downloadInfoChanged.connect(onDownloadInfoChanged);
|
||||
GlobalServices.updateDownloadInfo();
|
||||
Script.setInterval(update, 1000 / 60);
|
||||
Script.scriptEnding.connect(tearDown);
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
48
scripts/simplifiedUI/system/request-service.js
Normal file
48
scripts/simplifiedUI/system/request-service.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
"use strict";
|
||||
//
|
||||
// request-service.js
|
||||
//
|
||||
// Created by Howard Stearns on May 22, 2018
|
||||
// Copyright 2018 High Fidelity, Inc
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
// QML has its own XMLHttpRequest, but:
|
||||
// - npm request is easier to use.
|
||||
// - It is not easy to hack QML's XMLHttpRequest to use our MetaverseServer, and to supply the user's auth when contacting it.
|
||||
// a. Our custom XMLHttpRequestClass object only works with QScriptEngine, not QML's javascript.
|
||||
// b. We have hacked profiles that intercept requests to our MetavserseServer (providing the correct auth), but those
|
||||
// only work in QML WebEngineView. Setting up communication between ordinary QML and a hiddent WebEngineView is
|
||||
// tantamount to the following anyway, and would still have to duplicate the code from request.js.
|
||||
|
||||
// So, this script does two things:
|
||||
// 1. Allows any root .qml to signal sendToScript({id: aString, method: 'http.request', params: byNameOptions})
|
||||
// We will then asynchonously call fromScript({id: theSameString, method: 'http.response', error: errorOrFalsey, response: body})
|
||||
// on that root object.
|
||||
// RootHttpRequest.qml does this.
|
||||
// 2. If the uri used (computed from byNameOptions, see request.js) is to our metaverse, we will use the appropriate auth.
|
||||
|
||||
var request = Script.require('request').request;
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
function fromQml(message) { // messages are {id, method, params}, like json-rpc. See also sendToQml.
|
||||
switch (message.method) {
|
||||
case 'http.request':
|
||||
request(message.params, function (error, response) {
|
||||
tablet.sendToQml({
|
||||
id: message.id,
|
||||
method: 'http.response',
|
||||
error: error, // Alas, this isn't always a JSON-RPC conforming error object.
|
||||
response: response,
|
||||
jsonrpc: '2.0'
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
tablet.fromQml.connect(fromQml);
|
||||
Script.scriptEnding.connect(function () { tablet.fromQml.disconnect(fromQml); });
|
||||
}()); // END LOCAL_SCOPE
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
20
tools/ci-scripts/postbuild.py
Normal file
20
tools/ci-scripts/postbuild.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Post build script
|
||||
import os
|
||||
import sys
|
||||
|
||||
SOURCE_PATH = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '..'))
|
||||
BUILD_PATH = os.path.join(SOURCE_PATH, 'build')
|
||||
|
||||
# FIXME move the helper python modules somewher other than the root of the repo
|
||||
sys.path.append(SOURCE_PATH)
|
||||
|
||||
import hifi_utils
|
||||
|
||||
#for var in sys.argv:
|
||||
# print("{}".format(var))
|
||||
|
||||
#for var in os.environ:
|
||||
# print("{} = {}".format(var, os.environ[var]))
|
||||
|
||||
print("Create ZIP version of installer archive")
|
||||
hifi_utils.executeSubprocess(['cpack', '-G', 'ZIP'], folder=BUILD_PATH)
|
Loading…
Reference in a new issue