diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index 42124bf154..82fd86b3dd 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -162,7 +162,19 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea jvmArgs.version = JNI_VERSION_1_6; // choose your JNI version jvmArgs.name = NULL; // you might want to give the java thread a name jvmArgs.group = NULL; // you might want to assign the java thread to a ThreadGroup - jvm->AttachCurrentThread(reinterpret_cast(&myNewEnv), &jvmArgs); + + int attachedHere = 0; // know if detaching at the end is necessary + jint res = jvm->GetEnv((void**)&myNewEnv, JNI_VERSION_1_6); // checks if current env needs attaching or it is already attached + if (JNI_OK != res) { + qDebug() << "[JCRASH] GetEnv env not attached yet, attaching now.."; + res = jvm->AttachCurrentThread(reinterpret_cast(&myNewEnv), &jvmArgs); + if (JNI_OK != res) { + qDebug() << "[JCRASH] Failed to AttachCurrentThread, ErrorCode = " << res; + return; + } else { + attachedHere = 1; + } + } QAndroidJniObject string = QAndroidJniObject::fromString(a); jboolean jBackToScene = (jboolean) backToScene; @@ -175,7 +187,9 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea myNewEnv->CallObjectMethod(hashmap, mapClassPut, QAndroidJniObject::fromString("url").object(), jArg.object()); } __interfaceActivity.callMethod("openAndroidActivity", "(Ljava/lang/String;ZLjava/util/HashMap;)V", string.object(), jBackToScene, hashmap); - jvm->DetachCurrentThread(); + if (attachedHere) { + jvm->DetachCurrentThread(); + } }); QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) { diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 83dd633d22..07f1eb7e5e 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1223,7 +1223,7 @@ "name": "max_avatar_height", "type": "double", "label": "Maximum Avatar Height (meters)", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "help": "Limits the height of avatars in your domain. Cannot be greater than 1755.", "placeholder": 5.2, "default": 5.2 }, diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index e67ea43158..3888277c00 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -58,7 +58,11 @@ $(document).ready(function(){ } Settings.handlePostSettings = function(formJSON) { - + + if (!verifyAvatarHeights()) { + return false; + } + // check if we've set the basic http password if (formJSON["security"]) { @@ -207,7 +211,7 @@ $(document).ready(function(){ swal({ title: '', type: 'error', - text: "There was a problem retreiving domain information from High Fidelity API.", + text: "There was a problem retrieving domain information from High Fidelity API.", confirmButtonText: 'Try again', showCancelButton: true, closeOnConfirm: false @@ -288,7 +292,7 @@ $(document).ready(function(){ swal({ title: 'Create new domain ID', type: 'input', - text: 'Enter a label this machine.

This will help you identify which domain ID belongs to which machine.

', + text: 'Enter a label for this machine.

This will help you identify which domain ID belongs to which machine.

', showCancelButton: true, confirmButtonText: "Create", closeOnConfirm: false, @@ -669,7 +673,7 @@ $(document).ready(function(){ var spinner = createDomainSpinner(); $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); - var errorEl = createDomainLoadingError("There was an error retreiving your places."); + var errorEl = createDomainLoadingError("There was an error retrieving your places."); $("#" + Settings.PLACES_TABLE_ID).after(errorEl); // do we have a domain ID? @@ -1091,4 +1095,43 @@ $(document).ready(function(){ $('#settings_backup .panel-body').html(html); } + + function verifyAvatarHeights() { + var errorString = ''; + var minAllowedHeight = 0.009; + var maxAllowedHeight = 1755; + var alertCss = { backgroundColor: '#ffa0a0' }; + var minHeightElement = $('input[name="avatars.min_avatar_height"]'); + var maxHeightElement = $('input[name="avatars.max_avatar_height"]'); + + var minHeight = Number(minHeightElement.val()); + var maxHeight = Number(maxHeightElement.val()); + + if (maxHeight < minHeight) { + errorString = 'Maximum avatar height must not be less than minimum avatar height
'; + minHeightElement.css(alertCss); + maxHeightElement.css(alertCss); + }; + if (minHeight < minAllowedHeight) { + errorString += 'Minimum avatar height must not be less than ' + minAllowedHeight + '
'; + minHeightElement.css(alertCss); + } + if (maxHeight > maxAllowedHeight) { + errorString += 'Maximum avatar height must not be greater than ' + maxAllowedHeight + '
'; + maxHeightElement.css(alertCss); + } + + if (errorString.length > 0) { + swal({ + type: 'error', + title: '', + text: errorString, + html: true + }); + return false; + } else { + return true; + } + + } }); diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 135c1379e2..0a45feb61f 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -299,7 +299,7 @@ Item { anchors.fill: stackView id: controllerPrefereneces objectName: "TabletControllerPreferences" - showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] + showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] categoryProperties: { "VR Movement" : { "User real-world height (meters)" : { "anchors.right" : "undefined" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 311c08b858..6963f945aa 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3650,6 +3650,10 @@ bool Application::event(QEvent* event) { bool Application::eventFilter(QObject* object, QEvent* event) { + if (_aboutToQuit) { + return true; + } + if (event->type() == QEvent::Leave) { getApplicationCompositor().handleLeaveEvent(); } @@ -4839,6 +4843,13 @@ void Application::loadSettings() { } isFirstPerson = (qApp->isHMDMode()); + + // Flying should be disabled by default in HMD mode on first run, and it + // should be enabled by default in desktop mode. + + auto myAvatar = getMyAvatar(); + myAvatar->setFlyingEnabled(!isFirstPerson); + } else { // if this is not the first run, the camera will be initialized differently depending on user settings diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 4e3e539dea..3e0e643bd8 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -144,9 +144,19 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { emit bookmarkDeleted(bookmarkName); } +bool isWearableEntity(const EntityItemPointer& entity) { + return entity->isVisible() && entity->getParentJointIndex() != INVALID_JOINT_INDEX && + (entity->getParentID() == DependencyManager::get()->getSessionUUID() || entity->getParentID() == DependencyManager::get()->getMyAvatar()->getSelfID()); +} + void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - myAvatar->removeAvatarEntities(); + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + myAvatar->removeAvatarEntities([&](const QUuid& entityID) { + auto entity = entityTree->findEntityByID(entityID); + return entity && isWearableEntity(entity); + }); addAvatarEntities(avatarEntities); } @@ -163,7 +173,12 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { QVariantMap bookmark = bookmarkEntry.value().toMap(); if (!bookmark.empty()) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - myAvatar->removeAvatarEntities(); + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + myAvatar->removeAvatarEntities([&](const QUuid& entityID) { + auto entity = entityTree->findEntityByID(entityID); + return entity && isWearableEntity(entity); + }); const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); qCDebug(interfaceapp) << "Avatar On " << avatarUrl; @@ -233,6 +248,27 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); - bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); + + QScriptEngine scriptEngine; + QVariantList wearableEntities; + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + auto avatarEntities = myAvatar->getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + auto entity = entityTree->findEntityByID(entityID); + if (!entity || !isWearableEntity(entity)) { + continue; + } + QVariantMap avatarEntityData; + EncodeBitstreamParams params; + auto desiredProperties = entity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + EntityItemProperties entityProperties = entity->getProperties(desiredProperties); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + avatarEntityData["properties"] = scriptProperties.toVariant(); + wearableEntities.append(QVariant(avatarEntityData)); + } + bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities); return bookmark; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8524c40262..130c2c0b89 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -275,6 +275,13 @@ Menu::Menu() { QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); }); + // Settings > Attachments... + action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Attachments); + connect(action, &QAction::triggered, [] { + qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"), + QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog"); + }); + // Settings > Developer Menu addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus())); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fd121055a1..bf7276d5d5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1281,7 +1281,8 @@ void MyAvatar::loadData() { settings.remove("avatarEntityData"); } setAvatarEntityDataChanged(true); - setFlyingEnabled(settings.value("enabledFlying").toBool()); + Setting::Handle firstRunVal { Settings::firstRun, true }; + setFlyingEnabled(firstRunVal.get() ? getFlyingEnabled() : settings.value("enabledFlying").toBool()); setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); @@ -1607,14 +1608,16 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { emit skeletonModelURLChanged(); } -void MyAvatar::removeAvatarEntities() { +void MyAvatar::removeAvatarEntities(const std::function& condition) { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); for (auto entityID : avatarEntities.keys()) { - entityTree->deleteEntity(entityID, true, true); + if (!condition || condition(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } } }); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9b5ddd360d..add00962da 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -33,6 +33,7 @@ #include "MyCharacterController.h" #include "RingBufferHistory.h" #include +#include class AvatarActionHold; class ModelItemID; @@ -926,7 +927,7 @@ public: * @returns {object[]} */ Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); - void removeAvatarEntities(); + void removeAvatarEntities(const std::function& condition = {}); /**jsdoc * @function MyAvatar.isFlying diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index d0fe92ea00..135b59a152 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -266,20 +266,15 @@ void setupPreferences() { preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); } - static const QString MOVEMENT{ "VR Movement" }; + static const QString MOVEMENT{ "Movement" }; { static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(MOVEMENT, - QStringLiteral("Advanced movement for hand controllers"), - getter, setter)); - } - { - auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); }; - auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); }; - preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter)); + QStringLiteral("Advanced movement for hand controllers"), + getter, setter)); } { auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; @@ -307,6 +302,47 @@ void setupPreferences() { preferences->addPreference(preference); } + static const QString VR_MOVEMENT{ "VR Movement" }; + { + + static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); + auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; + auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, + QStringLiteral("Advanced movement for hand controllers"), + getter, setter)); + } + { + auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); }; + auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter)); + } + { + auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; + auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); }; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Snap turn / Smooth turn", getter, setter); + QStringList items; + items << "Snap turn" << "Smooth turn"; + preference->setItems(items); + preferences->addPreference(preference); + } + { + auto getter = [=]()->float { return myAvatar->getUserHeight(); }; + auto setter = [=](float value) { myAvatar->setUserHeight(value); }; + auto preference = new SpinnerPreference(VR_MOVEMENT, "User real-world height (meters)", getter, setter); + preference->setMin(1.0f); + preference->setMax(2.2f); + preference->setDecimals(3); + preference->setStep(0.001f); + preferences->addPreference(preference); + } + { + auto preference = new ButtonPreference(VR_MOVEMENT, "RESET SENSORS", [] { + qApp->resetSensors(); + }); + preferences->addPreference(preference); + } + static const QString AVATAR_CAMERA{ "Mouse Sensitivity" }; { auto getter = [=]()->float { return myAvatar->getPitchSpeed(); }; diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index d419a2fb7a..6ca7962c39 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,5 +1,8 @@ set(TARGET_NAME audio-client) -setup_hifi_library(Network Multimedia) +if (ANDROID) + set(PLATFORM_QT_COMPONENTS AndroidExtras) +endif () +setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a6f0416a30..c57360b09f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -52,6 +52,10 @@ #include "AudioLogging.h" #include "AudioHelpers.h" +#if defined(Q_OS_ANDROID) +#include +#endif + const int AudioClient::MIN_BUFFER_FRAMES = 1; const int AudioClient::MAX_BUFFER_FRAMES = 20; @@ -60,7 +64,7 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100; #if defined(Q_OS_ANDROID) static const int CHECK_INPUT_READS_MSECS = 2000; -static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 100; +static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 10; #endif static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; }; @@ -235,7 +239,7 @@ AudioClient::AudioClient() : // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); - connect(_checkDevicesTimer, &QTimer::timeout, [this] { + connect(_checkDevicesTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkDevices(); }); }); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; @@ -243,7 +247,7 @@ AudioClient::AudioClient() : // start a thread to detect peak value changes _checkPeakValuesTimer = new QTimer(this); - connect(_checkPeakValuesTimer, &QTimer::timeout, [this] { + connect(_checkPeakValuesTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkPeakValues(); }); }); const unsigned long PEAK_VALUES_CHECK_INTERVAL_MSECS = 50; @@ -482,6 +486,15 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, audioFormat.setSampleType(QAudioFormat::SignedInt); audioFormat.setByteOrder(QAudioFormat::LittleEndian); +#if defined(Q_OS_ANDROID) + // Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream + // Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB + QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); + if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { + audioFormat.setSampleRate(24000); + } +#endif + if (!audioDevice.isFormatSupported(audioFormat)) { qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed."; return false; @@ -635,9 +648,7 @@ void AudioClient::start() { qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.nearestFormat(_desiredOutputFormat); } #if defined(Q_OS_ANDROID) - connect(&_checkInputTimer, &QTimer::timeout, [this] { - checkInputTimeout(); - }); + connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); _checkInputTimer.start(CHECK_INPUT_READS_MSECS); #endif } @@ -651,6 +662,7 @@ void AudioClient::stop() { switchOutputToAudioDevice(QAudioDeviceInfo(), true); #if defined(Q_OS_ANDROID) _checkInputTimer.stop(); + disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0); #endif } @@ -1558,9 +1570,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf #if defined(Q_OS_ANDROID) if (_audioInput) { _shouldRestartInputSetup = true; - connect(_audioInput, &QAudioInput::stateChanged, [this](QAudio::State state) { - audioInputStateChanged(state); - }); + connect(_audioInput, &QAudioInput::stateChanged, this, &AudioClient::audioInputStateChanged); } #endif _inputDevice = _audioInput->start(); @@ -1764,7 +1774,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI _outputScratchBuffer = new int16_t[_outputPeriod]; // size local output mix buffer based on resampled network frame size - int networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); + int networkPeriod = _localToOutputResampler ? _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO) : AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; _localOutputMixBuffer = new float[networkPeriod]; // local period should be at least twice the output period, diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 78d3fad696..e135bc937d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -185,7 +185,7 @@ private: #if defined(Q_OS_ANDROID) Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; #else - Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true }; + Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; // False, until such time as it is made to work better. #endif #if (PR_BUILD || DEV_BUILD) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 4d4303698b..a068a806ec 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -229,7 +229,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc if (bytesWritten < 0) { // when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug - HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()) ); + HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error()); } return bytesWritten; @@ -513,7 +513,7 @@ std::vector Socket::getConnectionSockAddrs() { } void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { - HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError << _udpSocket.errorString()); + HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError); } void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index f692128fa3..b598649f4f 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -29,13 +29,25 @@ function executeLater(callback) { Script.setTimeout(callback, 300); } -function getMyAvatarWearables() { - var wearablesArray = MyAvatar.getAvatarEntitiesVariant(); +var INVALID_JOINT_INDEX = -1 +function isWearable(avatarEntity) { + return avatarEntity.properties.visible === true && avatarEntity.properties.parentJointIndex !== INVALID_JOINT_INDEX && + (avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID); +} - for(var i = 0; i < wearablesArray.length; ++i) { - var wearable = wearablesArray[i]; - var localRotation = wearable.properties.localRotation; - wearable.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) +function getMyAvatarWearables() { + var entitiesArray = MyAvatar.getAvatarEntitiesVariant(); + var wearablesArray = []; + + for (var i = 0; i < entitiesArray.length; ++i) { + var entity = entitiesArray[i]; + if (!isWearable(entity)) { + continue; + } + + var localRotation = entity.properties.localRotation; + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) + wearablesArray.push(entity); } return wearablesArray; @@ -159,13 +171,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See for(var bookmarkName in message.data.bookmarks) { var bookmark = message.data.bookmarks[bookmarkName]; - if (!bookmark.avatarEntites) { // ensure avatarEntites always exist - bookmark.avatarEntites = []; - } - bookmark.avatarEntites.forEach(function(avatarEntity) { - avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation) - }) + if (bookmark.avatarEntites) { + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + } } sendToQml(message) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 96a3b2b015..091d431502 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -263,7 +263,7 @@ SelectionManager = (function() { that.worldDimensions = properties.boundingBox.dimensions; that.worldPosition = properties.boundingBox.center; - that.worldRotation = properties.boundingBox.rotation; + that.worldRotation = Quat.IDENTITY; that.entityType = properties.type; @@ -271,10 +271,6 @@ SelectionManager = (function() { SelectionDisplay.setSpaceMode(SPACE_LOCAL); } } else { - that.localRotation = null; - that.localDimensions = null; - that.localPosition = null; - properties = Entities.getEntityProperties(that.selections[0]); that.entityType = properties.type; @@ -293,6 +289,7 @@ SelectionManager = (function() { tfl.z = Math.max(bb.tfl.z, tfl.z); } + that.localRotation = null; that.localDimensions = null; that.localPosition = null; that.worldDimensions = { @@ -300,6 +297,7 @@ SelectionManager = (function() { y: tfl.y - brn.y, z: tfl.z - brn.z }; + that.worldRotation = Quat.IDENTITY; that.worldPosition = { x: brn.x + (that.worldDimensions.x / 2), y: brn.y + (that.worldDimensions.y / 2), @@ -381,6 +379,8 @@ SelectionDisplay = (function() { var CTRL_KEY_CODE = 16777249; + var RAIL_AXIS_LENGTH = 10000; + var TRANSLATE_DIRECTION = { X: 0, Y: 1, @@ -616,6 +616,40 @@ SelectionDisplay = (function() { dashed: false }); + var xRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true // always ignore this + }); + var yRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 255, + blue: 0 + }, + ignoreRayIntersection: true // always ignore this + }); + var zRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignoreRayIntersection: true // always ignore this +}); + var allOverlays = [ handleTranslateXCone, handleTranslateXCylinder, @@ -656,7 +690,11 @@ SelectionDisplay = (function() { handleScaleFLEdge, handleCloner, selectionBox, - iconSelectionBox + iconSelectionBox, + xRailOverlay, + yRailOverlay, + zRailOverlay + ]; var maximumHandleInAllOverlays = handleCloner; @@ -873,11 +911,13 @@ SelectionDisplay = (function() { }; // FUNCTION: MOUSE MOVE EVENT + var lastMouseEvent = null; that.mouseMoveEvent = function(event) { var wantDebug = false; if (wantDebug) { print("=============== eST::MouseMoveEvent BEG ======================="); } + lastMouseEvent = event; if (activeTool) { if (wantDebug) { print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); @@ -999,19 +1039,35 @@ SelectionDisplay = (function() { }; // Control key remains active only while key is held down - that.keyReleaseEvent = function(key) { - if (key.key === CTRL_KEY_CODE) { + that.keyReleaseEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { ctrlPressed = false; that.updateActiveRotateRing(); } + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); + SelectionManager._update(); + } }; // Triggers notification on specific key driven events - that.keyPressEvent = function(key) { - if (key.key === CTRL_KEY_CODE) { + that.keyPressEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { ctrlPressed = true; that.updateActiveRotateRing(); } + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); + SelectionManager._update(); + } }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: @@ -1705,6 +1761,14 @@ SelectionDisplay = (function() { }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); + } }, elevation: function(origin, intersection) { return (origin.y - intersection.y) / Vec3.distance(origin, intersection); @@ -1768,10 +1832,46 @@ SelectionDisplay = (function() { vector.x = 0; } if (!isConstrained) { + var xStart = Vec3.sum(startPosition, { + x: -RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var xEnd = Vec3.sum(startPosition, { + x: RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var zStart = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: -RAIL_AXIS_LENGTH + }); + var zEnd = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: RAIL_AXIS_LENGTH + }); + Overlays.editOverlay(xRailOverlay, { + start: xStart, + end: xEnd, + visible: true + }); + Overlays.editOverlay(zRailOverlay, { + start: zStart, + end: zEnd, + visible: true + }); isConstrained = true; } } else { if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); isConstrained = false; } } diff --git a/server-console/src/main.js b/server-console/src/main.js index dbbe699325..92ebdbf36c 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -405,7 +405,7 @@ LogWindow.prototype = { } }; -function goHomeClicked() { +function visitSandboxClicked() { if (interfacePath) { startInterface('hifi://localhost'); } else { @@ -439,8 +439,8 @@ var labels = { } }, goHome: { - label: 'Go Home', - click: goHomeClicked, + label: 'Visit Sandbox', + click: visitSandboxClicked, enabled: false }, quit: {