diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index c7bca428e9..760fdcb0b5 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -210,11 +210,13 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDest JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) { QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url); DependencyManager::get()->loadSettings(jniUrl.toString()); + AndroidHelper::instance().muteMic(); } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoToUser(JNIEnv* env, jobject obj, jstring username) { QAndroidJniObject jniUsername("java/lang/String", "(Ljava/lang/String;)V", username); DependencyManager::get()->goToUser(jniUsername.toString(), false); + AndroidHelper::instance().muteMic(); } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java index cc23665e72..58b6638531 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java @@ -3,8 +3,8 @@ package io.highfidelity.hifiinterface.fragment; import android.content.SharedPreferences; import android.media.audiofx.AcousticEchoCanceler; import android.os.Bundle; -import android.preference.Preference; import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; import android.support.annotation.Nullable; import io.highfidelity.hifiinterface.R; @@ -18,17 +18,23 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer private final String HIFI_SETTINGS_AEC_KEY = "aec"; private final String PREFERENCE_KEY_AEC = "aec"; + private final boolean DEFAULT_AEC_ENABLED = true; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings); + boolean aecAvailable = AcousticEchoCanceler.isAvailable(); + PreferenceManager.setDefaultValues(getContext(), R.xml.settings, false); - if (!AcousticEchoCanceler.isAvailable()) { - getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false); + if (!aecAvailable) { + findPreference(PREFERENCE_KEY_AEC).setEnabled(false); + updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false); } getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC, - getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false)); + aecAvailable && getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, DEFAULT_AEC_ENABLED)).commit(); + } public static SettingsFragment newInstance() { @@ -46,15 +52,13 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer public void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); - } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - Preference pref = findPreference(key); switch (key) { - case "aec": - updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false)); + case PREFERENCE_KEY_AEC: + updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, DEFAULT_AEC_ENABLED)); break; default: break; diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml index 5ec47b1aff..934d34ba73 100644 --- a/android/app/src/main/res/xml/settings.xml +++ b/android/app/src/main/res/xml/settings.xml @@ -6,6 +6,7 @@ + android:summary="@string/acoustic_echo_cancellation" + android:defaultValue="true" /> \ No newline at end of file diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index afd4047c68..d6f893c42e 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -89,7 +89,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : PacketType::NodeIgnoreRequest, PacketType::RadiusIgnoreRequest, PacketType::RequestsDomainListData, - PacketType::PerAvatarGainSet }, + PacketType::PerAvatarGainSet, + PacketType::AudioSoloRequest }, this, "queueAudioPacket"); // packets whose consequences are global should be processed on the main thread diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index e921bf8ed2..9a78ba31a2 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -98,6 +98,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) { case PacketType::RadiusIgnoreRequest: parseRadiusIgnoreRequest(packet, node); break; + case PacketType::AudioSoloRequest: + parseSoloRequest(packet, node); + break; default: Q_UNREACHABLE(); } @@ -295,6 +298,25 @@ void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer message, const SharedNodePointer& node) { + + uint8_t addToSolo; + message->readPrimitive(&addToSolo); + + while (message->getBytesLeftToRead()) { + // parse out the UUID being soloed from the packet + QUuid soloedUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + if (addToSolo) { + _soloedNodes.push_back(soloedUUID); + } else { + auto it = std::remove(std::begin(_soloedNodes), std::end(_soloedNodes), soloedUUID); + _soloedNodes.erase(it, std::end(_soloedNodes)); + } + } +} + AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() { auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){ return stream->getStreamIdentifier().isNull(); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 0a66a8164b..653749f619 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -65,6 +65,7 @@ public: void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node); void parseNodeIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); void parseRadiusIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); + void parseSoloRequest(QSharedPointer message, const SharedNodePointer& node); // attempt to pop a frame from each audio stream, and return the number of streams from this client int checkBuffersBeforeFrameSend(); @@ -150,6 +151,9 @@ public: const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; } + + const std::vector& getSoloedNodes() const { return _soloedNodes; } + bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; } void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; } @@ -209,6 +213,8 @@ private: std::atomic_bool _isIgnoreRadiusEnabled { false }; + std::vector _soloedNodes; + bool _hasReceivedFirstMix { false }; }; diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 57bada47f1..7a6ab9c3e2 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener, return true; } + if (!listenerData.getSoloedNodes().empty()) { + return !contains(listenerData.getSoloedNodes(), stream.nodeStreamID.nodeID); + } + bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() || stream.positionalStream->isIgnoreBoxEnabled()); if (shouldCheckIgnoreBox && @@ -310,6 +314,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) { memset(_mixSamples, 0, sizeof(_mixSamples)); bool isThrottling = _numToRetain != -1; + bool isSoloing = !listenerData->getSoloedNodes().empty(); auto& streams = listenerData->getStreams(); @@ -376,13 +381,14 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) { stream.approximateVolume = approximateVolume(stream, listenerAudioStream); } else { if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) { - addStream(stream, *listenerAudioStream, 0.0f); + addStream(stream, *listenerAudioStream, 0.0f, isSoloing); streams.skipped.push_back(move(stream)); ++stats.activeToSkipped; return true; } - addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain()); + addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), + isSoloing); if (shouldBeInactive(stream)) { // To reduce artifacts we still call render to flush the HRTF for every silent @@ -417,7 +423,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) { return true; } - addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain()); + addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), + isSoloing); if (shouldBeInactive(stream)) { // To reduce artifacts we still call render to flush the HRTF for every silent @@ -484,7 +491,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) { void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream, AvatarAudioStream& listeningNodeStream, - float masterListenerGain) { + float masterListenerGain, bool isSoloing) { ++stats.totalMixes; auto streamToAdd = mixableStream.positionalStream; @@ -495,9 +502,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition(); float distance = glm::max(glm::length(relativePosition), EPSILON); - float gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); + float gain = 1.0f; + if (!isSoloing) { + gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho); + } + const int HRTF_DATASET_INDEX = 1; if (!streamToAdd->lastPopSucceeded()) { diff --git a/assignment-client/src/audio/AudioMixerSlave.h b/assignment-client/src/audio/AudioMixerSlave.h index 6566c839b8..3d979da1fc 100644 --- a/assignment-client/src/audio/AudioMixerSlave.h +++ b/assignment-client/src/audio/AudioMixerSlave.h @@ -57,7 +57,7 @@ private: bool prepareMix(const SharedNodePointer& listener); void addStream(AudioMixerClientData::MixableStream& mixableStream, AvatarAudioStream& listeningNodeStream, - float masterListenerGain); + float masterListenerGain, bool isSoloing); void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream, AvatarAudioStream& listeningNodeStream, float masterListenerGain); diff --git a/interface/resources/icons/tablet-icons/inventory-a-msg.svg b/interface/resources/icons/tablet-icons/inventory-a-msg.svg index 794bd1e414..b028c08b50 100644 --- a/interface/resources/icons/tablet-icons/inventory-a-msg.svg +++ b/interface/resources/icons/tablet-icons/inventory-a-msg.svg @@ -1,4 +1,20 @@ - - - - + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/inventory-a.svg b/interface/resources/icons/tablet-icons/inventory-a.svg index 8b6f34eaa3..584ebd3b9b 100644 --- a/interface/resources/icons/tablet-icons/inventory-a.svg +++ b/interface/resources/icons/tablet-icons/inventory-a.svg @@ -1,3 +1,16 @@ - - + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/inventory-i-msg.svg b/interface/resources/icons/tablet-icons/inventory-i-msg.svg index 35d4fb54ae..d2abc7b7ca 100644 --- a/interface/resources/icons/tablet-icons/inventory-i-msg.svg +++ b/interface/resources/icons/tablet-icons/inventory-i-msg.svg @@ -1,4 +1,21 @@ - - - - + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/inventory-i.svg b/interface/resources/icons/tablet-icons/inventory-i.svg index 071fabce88..733ea6df9a 100644 --- a/interface/resources/icons/tablet-icons/inventory-i.svg +++ b/interface/resources/icons/tablet-icons/inventory-i.svg @@ -1,3 +1,19 @@ - - + + + + + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 81fec4ace3..37ce50b469 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -536,8 +536,8 @@ Rectangle { Rectangle { id: exchangeMoneyMessagesWaitingLight; visible: parent.messagesWaiting; - anchors.right: exchangeMoneyTabIcon.left; - anchors.rightMargin: 9; + anchors.left: parent.left; + anchors.leftMargin: 16; anchors.top: exchangeMoneyTabIcon.top; anchors.topMargin: 4; height: 10; diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index ae6aa34cc9..708859c00a 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -73,6 +73,14 @@ void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { #endif } +void AndroidHelper::muteMic() { + auto audioClient = DependencyManager::get(); + if (audioClient) { + QMetaObject::invokeMethod(audioClient.data(), "setMuted", Q_ARG(bool, true), Q_ARG(bool, true)); + } +} + + void AndroidHelper::signup(QString email, QString username, QString password) { JSONCallbackParameters callbackParams; callbackParams.callbackReceiver = this; diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index f1917deca0..f0aaa84214 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -35,6 +35,7 @@ public: void performHapticFeedback(int duration); void processURL(const QString &url); void notifyHeadsetOn(bool pluggedIn); + void muteMic(); AndroidHelper(AndroidHelper const&) = delete; void operator=(AndroidHelper const&) = delete; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 876e64c592..6b2264c924 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -538,17 +538,25 @@ void MyAvatar::update(float deltaTime) { // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter - if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { - glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y)); + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); + if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && !(spine2Index < 0)) { + + // use the spine for the azimuth origin. + glm::quat spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index); + glm::vec3 handHipAzimuthAvatarSpace = spine2Rot * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y); + glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), handHipAzimuthAvatarSpace); glm::mat4 sensorToWorldMat = getSensorToWorldMatrix(); glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat); glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace); glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f); if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) { normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)); + glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH); + _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau); + } else { + // use head facing if the chest arms vector is up or down. + _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); } - glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH); - _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau); } else { _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); } @@ -979,35 +987,48 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { } // Find the vector halfway between the hip to hand azimuth vectors -// This midpoint hand azimuth is in Avatar space +// This midpoint hand azimuth is in Spine2 space glm::vec2 MyAvatar::computeHandAzimuth() const { controller::Pose leftHandPoseAvatarSpace = getLeftHandPose(); controller::Pose rightHandPoseAvatarSpace = getRightHandPose(); controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD); const float HALFWAY = 0.50f; + glm::vec2 latestHipToHandController = _hipToHandController; - if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid()) { + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); + if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid() && !(spine2Index < 0)) { + + glm::vec3 spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index); + glm::quat spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index); + + glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - spine2Position; + glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - spine2Position; + glm::vec3 rightHandSpine2Space = glm::inverse(spine2Rotation) * rightHandOffset; + glm::vec3 leftHandSpine2Space = glm::inverse(spine2Rotation) * leftHandOffset; + // we need the old azimuth reading to prevent flipping the facing direction 180 // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart. glm::vec2 oldAzimuthReading = _hipToHandController; - if ((glm::length(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)) > 0.0f) && (glm::length(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)) > 0.0f)) { - latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)), glm::normalize(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)), HALFWAY); + if ((glm::length(glm::vec2(rightHandSpine2Space.x, rightHandSpine2Space.z)) > 0.0f) && (glm::length(glm::vec2(leftHandSpine2Space.x, leftHandSpine2Space.z)) > 0.0f)) { + latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandSpine2Space.x, rightHandSpine2Space.z)), glm::normalize(glm::vec2(leftHandSpine2Space.x, leftHandSpine2Space.z)), HALFWAY); } else { - latestHipToHandController = glm::vec2(0.0f, -1.0f); + latestHipToHandController = glm::vec2(0.0f, 1.0f); } glm::vec3 headLookAtAvatarSpace = transformVectorFast(headPoseAvatarSpace.getMatrix(), glm::vec3(0.0f, 0.0f, 1.0f)); - glm::vec2 headAzimuthAvatarSpace = glm::vec2(headLookAtAvatarSpace.x, headLookAtAvatarSpace.z); - if (glm::length(headAzimuthAvatarSpace) > 0.0f) { - headAzimuthAvatarSpace = glm::normalize(headAzimuthAvatarSpace); + glm::vec3 headLookAtSpine2Space = glm::inverse(spine2Rotation) * headLookAtAvatarSpace; + + glm::vec2 headAzimuthSpine2Space = glm::vec2(headLookAtSpine2Space.x, headLookAtSpine2Space.z); + if (glm::length(headAzimuthSpine2Space) > 0.0f) { + headAzimuthSpine2Space = glm::normalize(headAzimuthSpine2Space); } else { - headAzimuthAvatarSpace = -latestHipToHandController; + headAzimuthSpine2Space = -latestHipToHandController; } // check the angular distance from forward and back float cosForwardAngle = glm::dot(latestHipToHandController, oldAzimuthReading); - float cosHeadShoulder = glm::dot(-latestHipToHandController, headAzimuthAvatarSpace); + float cosHeadShoulder = glm::dot(-latestHipToHandController, headAzimuthSpine2Space); // if we are now closer to the 180 flip of the previous chest forward // then we negate our computed latestHipToHandController to keep the chest from flipping. // also check the head to shoulder azimuth difference if we negate. @@ -3518,19 +3539,33 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { } glm::mat4 MyAvatar::getSpine2RotationRigSpace() const { + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); + glm::quat spine2Rot = Quaternions::IDENTITY; + if (!(spine2Index < 0)) { + // use the spine for the azimuth origin. + spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index); + } + glm::vec3 spine2UpAvatarSpace = spine2Rot * glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 spine2FwdAvatarSpace = spine2Rot * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y); // static const glm::quat RIG_CHANGE_OF_BASIS = Quaternions::Y_180; // RIG_CHANGE_OF_BASIS * AVATAR_TO_RIG_ROTATION * inverse(RIG_CHANGE_OF_BASIS) = Quaternions::Y_180; //avatar Space; const glm::quat AVATAR_TO_RIG_ROTATION = Quaternions::Y_180; - glm::vec3 hipToHandRigSpace = AVATAR_TO_RIG_ROTATION * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y); + glm::vec3 spine2UpRigSpace = AVATAR_TO_RIG_ROTATION * spine2UpAvatarSpace; + glm::vec3 spine2FwdRigSpace = AVATAR_TO_RIG_ROTATION * spine2FwdAvatarSpace; glm::vec3 u, v, w; - if (glm::length(hipToHandRigSpace) > 0.0f) { - hipToHandRigSpace = glm::normalize(hipToHandRigSpace); + if (glm::length(spine2FwdRigSpace) > 0.0f) { + spine2FwdRigSpace = glm::normalize(spine2FwdRigSpace); } else { - hipToHandRigSpace = glm::vec3(0.0f, 0.0f, 1.0f); + spine2FwdRigSpace = glm::vec3(0.0f, 0.0f, 1.0f); } - generateBasisVectors(glm::vec3(0.0f,1.0f,0.0f), hipToHandRigSpace, u, v, w); + if (glm::length(spine2UpRigSpace) > 0.0f) { + spine2UpRigSpace = glm::normalize(spine2UpRigSpace); + } else { + spine2UpRigSpace = glm::vec3(0.0f, 1.0f, 0.0f); + } + generateBasisVectors(spine2UpRigSpace, spine2FwdRigSpace, u, v, w); glm::mat4 spine2RigSpace(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)); return spine2RigSpace; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 799427530a..85ad6fe7a4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1731,7 +1731,7 @@ private: glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space) glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f }; - glm::vec2 _hipToHandController { 0.0f, -1.0f }; // spine2 facing vector in xz plane (avatar space) + glm::vec2 _hipToHandController { 0.0f, 1.0f }; // spine2 facing vector in xz plane (spine2 space) float _currentStandingHeight { 0.0f }; bool _resetMode { true }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 2a21f78b21..524292b004 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -233,6 +233,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + const float SPINE2_ROTATION_FILTER = 0.5f; AnimPose currentSpine2Pose; AnimPose currentHeadPose; AnimPose currentHipsPose; @@ -252,7 +253,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); - currentSpine2Pose.rot() = newSpinePose.rot(); + currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 4b8eb6aabc..4c4bf6dd60 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -50,6 +50,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * Read-only. * @property {object} devices Read-only. Deprecated: This property is deprecated and will be * removed. + * @property {boolean} isSoloing Read-only. true if any nodes are soloed. + * @property {Uuid[]} soloList Read-only. Get the list of currently soloed node UUIDs. */ Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index bdd6d0edc1..92f7a27853 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -270,7 +270,8 @@ AudioClient::AudioClient() : configureReverb(); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket"); packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket"); packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket"); @@ -278,6 +279,16 @@ AudioClient::AudioClient() : packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); + + auto& domainHandler = nodeList->getDomainHandler(); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] { + _solo.reset(); + }); + connect(nodeList.data(), &NodeList::nodeActivated, this, [this](SharedNodePointer node) { + if (node->getType() == NodeType::AudioMixer) { + _solo.resend(); + } + }); } AudioClient::~AudioClient() { @@ -472,7 +483,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { - Setting::Handle enableAEC(SETTING_AEC_KEY, false); + Setting::Handle enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED); bool aecEnabled = enableAEC.get(); auto audioClient = DependencyManager::get(); bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false; @@ -1680,7 +1691,7 @@ void AudioClient::setHeadsetPluggedIn(bool pluggedIn) { QThread::msleep(200); } - Setting::Handle enableAEC(SETTING_AEC_KEY, false); + Setting::Handle enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED); bool aecEnabled = enableAEC.get(); if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 5e7f1fb8a0..f3e1ad9a52 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -46,7 +46,6 @@ #include #include - #include #include @@ -69,6 +68,7 @@ #define VOICE_COMMUNICATION "voicecommunication" #define SETTING_AEC_KEY "Android/aec" +#define DEFAULT_AEC_ENABLED true #endif class QAudioInput; @@ -171,6 +171,7 @@ public: void stopRecording(); void setAudioPaused(bool pause); + AudioSolo& getAudioSolo() override { return _solo; } #ifdef Q_OS_WIN static QString getWinDeviceName(wchar_t* guid); @@ -446,6 +447,8 @@ private: #if defined(Q_OS_ANDROID) bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop? #endif + + AudioSolo _solo; Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index bbfd79d0aa..0f075ab224 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -19,6 +19,7 @@ #include "AudioInjectorOptions.h" #include "AudioInjector.h" +#include "AudioSolo.h" class AudioInjector; class AudioInjectorLocalBuffer; @@ -38,6 +39,8 @@ public: // take care to delete it when ~AudioInjector, as parenting Qt semantics will not work virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0; + virtual AudioSolo& getAudioSolo() = 0; + public slots: virtual bool shouldLoopbackInjectors() { return false; } diff --git a/libraries/audio/src/AudioSolo.cpp b/libraries/audio/src/AudioSolo.cpp new file mode 100644 index 0000000000..9d63f01a8b --- /dev/null +++ b/libraries/audio/src/AudioSolo.cpp @@ -0,0 +1,88 @@ +// +// AudioSolo.cpp +// libraries/audio/src +// +// Created by Clement Brisset on 11/5/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 +// + +#include "AudioSolo.h" + +#include + +bool AudioSolo::isSoloing() const { + Lock lock(_mutex); + return !_nodesSoloed.empty(); +} + +QVector AudioSolo::getUUIDs() const { + Lock lock(_mutex); + return _nodesSoloed.values().toVector(); +} + +void AudioSolo::addUUIDs(QVector uuidList) { + // create a reliable NLPacket with space for the solo UUIDs + auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest, + uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true); + uint8_t addToSoloList = (uint8_t)true; + soloPacket->writePrimitive(addToSoloList); + + { + Lock lock(_mutex); + for (auto uuid : uuidList) { + if (_nodesSoloed.contains(uuid)) { + qWarning() << "Uuid already in solo list:" << uuid; + } else { + // write the node ID to the packet + soloPacket->write(uuid.toRfc4122()); + _nodesSoloed.insert(uuid); + } + } + } + + // send off this solo packet reliably to the matching node + auto nodeList = DependencyManager::get(); + nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer }); +} + +void AudioSolo::removeUUIDs(QVector uuidList) { + // create a reliable NLPacket with space for the solo UUIDs + auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest, + uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true); + uint8_t addToSoloList = (uint8_t)false; + soloPacket->writePrimitive(addToSoloList); + + { + Lock lock(_mutex); + for (auto uuid : uuidList) { + if (!_nodesSoloed.contains(uuid)) { + qWarning() << "Uuid not in solo list:" << uuid; + } else { + // write the node ID to the packet + soloPacket->write(uuid.toRfc4122()); + _nodesSoloed.remove(uuid); + } + } + } + + // send off this solo packet reliably to the matching node + auto nodeList = DependencyManager::get(); + nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer }); +} + +void AudioSolo::reset() { + Lock lock(_mutex); + removeUUIDs(getUUIDs()); +} + + +void AudioSolo::resend() { + Lock lock(_mutex); + auto uuids = getUUIDs(); + _nodesSoloed.clear(); + addUUIDs(uuids); +} + diff --git a/libraries/audio/src/AudioSolo.h b/libraries/audio/src/AudioSolo.h new file mode 100644 index 0000000000..790280a14b --- /dev/null +++ b/libraries/audio/src/AudioSolo.h @@ -0,0 +1,40 @@ +// +// AudioSolo.h +// libraries/audio/src +// +// Created by Clement Brisset on 11/5/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 +// + +#pragma once + +#ifndef hifi_AudioSolo_h +#define hifi_AudioSolo_h + +#include + +#include +#include + +class AudioSolo { + using Mutex = std::recursive_mutex; + using Lock = std::unique_lock; + +public: + bool isSoloing() const; + QVector getUUIDs() const; + void addUUIDs(QVector uuidList); + void removeUUIDs(QVector uuidList); + void reset(); + + void resend(); + +private: + mutable Mutex _mutex; + QSet _nodesSoloed; +}; + +#endif // hifi_AudioSolo_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f694148b30..03938dd3de 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1724,22 +1724,23 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { } void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { - // FIXME: this doesn't take into account Avatar rotation ShapeInfo shapeInfo; computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight - start = getWorldPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); - end = getWorldPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = cylinderHalfHeight + radius radius = halfExtents.x; + glm::vec3 halfCylinderAxis(0.0f, halfExtents.y - radius, 0.0f); + Transform transform = getTransform(); + start = transform.getTranslation() + transform.getRotation() * (shapeInfo.getOffset() - halfCylinderAxis); + end = transform.getTranslation() + transform.getRotation() * (shapeInfo.getOffset() + halfCylinderAxis); } glm::vec3 Avatar::getWorldFeetPosition() { ShapeInfo shapeInfo; - computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight - glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f); - return getWorldOrientation() * localFeet + getWorldPosition(); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = cylinderHalfHeight + radius + glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y, 0.0f); + Transform transform = getTransform(); + return transform.getTranslation() + transform.getRotation() * localFeet; } float Avatar::computeMass() { diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 37a4b32940..e5efe05ad0 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -126,14 +126,13 @@ public: EntityScriptCallMethod, ChallengeOwnershipRequest, ChallengeOwnershipReply, - OctreeDataFileRequest, OctreeDataFileReply, OctreeDataPersist, - EntityClone, EntityQueryInitialResultsComplete, BulkAvatarTraits, + AudioSoloRequest, NUM_PACKET_TYPE }; diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 8057eb0e0c..d7ba2f0661 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -307,21 +307,21 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) case SHAPE_TYPE_CAPSULE_Y: { glm::vec3 halfExtents = info.getHalfExtents(); float radius = halfExtents.x; - float height = 2.0f * halfExtents.y; + float height = 2.0f * (halfExtents.y - radius); shape = new btCapsuleShape(radius, height); } break; case SHAPE_TYPE_CAPSULE_X: { glm::vec3 halfExtents = info.getHalfExtents(); float radius = halfExtents.y; - float height = 2.0f * halfExtents.x; + float height = 2.0f * (halfExtents.x - radius); shape = new btCapsuleShapeX(radius, height); } break; case SHAPE_TYPE_CAPSULE_Z: { glm::vec3 halfExtents = info.getHalfExtents(); float radius = halfExtents.x; - float height = 2.0f * halfExtents.z; + float height = 2.0f * (halfExtents.z - radius); shape = new btCapsuleShapeZ(radius, height); } break; diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 1220a9b769..2854445b4f 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -25,13 +25,49 @@ class AudioScriptingInterface : public QObject, public Dependency { // JSDoc for property is in Audio.h. Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) + Q_PROPERTY(bool isSoloing READ isSoloing) + Q_PROPERTY(QVector soloList READ getSoloList) public: - virtual ~AudioScriptingInterface() {} + virtual ~AudioScriptingInterface() = default; void setLocalAudioInterface(AbstractAudioInterface* audioInterface); + bool isSoloing() const { + return _localAudioInterface->getAudioSolo().isSoloing(); + } + + QVector getSoloList() const { + return _localAudioInterface->getAudioSolo().getUUIDs(); + } + + /**jsdoc + * Add nodes to the audio solo list + * @function Audio.addToSoloList + * @param {Uuid[]} uuidList - List of node UUIDs to add to the solo list. + */ + Q_INVOKABLE void addToSoloList(QVector uuidList) { + _localAudioInterface->getAudioSolo().addUUIDs(uuidList); + } + + /**jsdoc + * Remove nodes from the audio solo list + * @function Audio.removeFromSoloList + * @param {Uuid[]} uuidList - List of node UUIDs to remove from the solo list. + */ + Q_INVOKABLE void removeFromSoloList(QVector uuidList) { + _localAudioInterface->getAudioSolo().removeUUIDs(uuidList); + } + + /**jsdoc + * Reset the list of soloed nodes. + * @function Audio.resetSoloList + */ + Q_INVOKABLE void resetSoloList() { + _localAudioInterface->getAudioSolo().reset(); + } + protected: - AudioScriptingInterface() {} + AudioScriptingInterface() = default; // these methods are protected to stop C++ callers from calling, but invokable from script diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 152e305bf2..df8e61114d 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -148,12 +148,12 @@ void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollec _hashKey.clear(); } -void ShapeInfo::setCapsuleY(float radius, float halfHeight) { +void ShapeInfo::setCapsuleY(float radius, float cylinderHalfHeight) { _url = ""; _type = SHAPE_TYPE_CAPSULE_Y; radius = glm::max(radius, MIN_HALF_EXTENT); - halfHeight = glm::max(halfHeight, 0.0f); - _halfExtents = glm::vec3(radius, halfHeight, radius); + cylinderHalfHeight = glm::max(cylinderHalfHeight, 0.0f); + _halfExtents = glm::vec3(radius, cylinderHalfHeight + radius, radius); _hashKey.clear(); } @@ -261,27 +261,27 @@ bool ShapeInfo::contains(const glm::vec3& point) const { case SHAPE_TYPE_CYLINDER_Z: return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y; case SHAPE_TYPE_CAPSULE_X: { - if (glm::abs(point.x) <= _halfExtents.x) { - return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; + if (glm::abs(point.x) <= _halfExtents.x - _halfExtents.y) { + return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.y; } else { - glm::vec3 absPoint = glm::abs(point) - _halfExtents.x; - return glm::length(absPoint) <= _halfExtents.z; + glm::vec3 absPoint = glm::abs(point) - glm::vec3(_halfExtents.x, 0.0f, 0.0f); + return glm::length(absPoint) <= _halfExtents.y; } } case SHAPE_TYPE_CAPSULE_Y: { - if (glm::abs(point.y) <= _halfExtents.y) { - return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.x; + if (glm::abs(point.y) <= _halfExtents.y - _halfExtents.z) { + return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.z; } else { - glm::vec3 absPoint = glm::abs(point) - _halfExtents.y; - return glm::length(absPoint) <= _halfExtents.x; + glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, _halfExtents.y, 0.0f); + return glm::length(absPoint) <= _halfExtents.z; } } case SHAPE_TYPE_CAPSULE_Z: { - if (glm::abs(point.z) <= _halfExtents.z) { - return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y; + if (glm::abs(point.z) <= _halfExtents.z - _halfExtents.x) { + return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.x; } else { - glm::vec3 absPoint = glm::abs(point) - _halfExtents.z; - return glm::length(absPoint) <= _halfExtents.y; + glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, 0.0f, _halfExtents.z); + return glm::length(absPoint) <= _halfExtents.x; } } case SHAPE_TYPE_BOX: diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 5020e492cf..16e260d9db 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -67,7 +67,7 @@ public: void setBox(const glm::vec3& halfExtents); void setSphere(float radius); void setPointCollection(const PointCollection& pointCollection); - void setCapsuleY(float radius, float halfHeight); + void setCapsuleY(float radius, float cylinderHalfHeight); void setOffset(const glm::vec3& offset); ShapeType getType() const { return _type; } diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 1aba6b92f6..44aa04b497 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -358,9 +358,9 @@ Script.include("/~/system/libraries/controllers.js"); var sensorToWorldScale = MyAvatar.getSensorToWorldScale(); - var radius = capsuleData.radius / sensorToWorldScale; - var height = (Vec3.distance(capsuleData.start, capsuleData.end) + (capsuleData.radius * 2.0)) / sensorToWorldScale; - var capsuleRatio = 10.0 * radius / height; + var diameter = 2.0 * capsuleData.radius / sensorToWorldScale; + var height = (Vec3.distance(capsuleData.start, capsuleData.end) + diameter) / sensorToWorldScale; + var capsuleRatio = 5.0 * diameter / height; var offset = _this.pickHeightOffset * capsuleRatio; _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, { @@ -370,9 +370,9 @@ Script.include("/~/system/libraries/controllers.js"); shape: { shapeType: "capsule-y", dimensions: { - x: radius * 2.0, - y: height - (radius * 2.0), - z: radius * 2.0 + x: diameter, + y: height, + z: diameter } }, position: { x: 0, y: offset + height * 0.5, z: 0 }, @@ -386,9 +386,9 @@ Script.include("/~/system/libraries/controllers.js"); shape: { shapeType: "capsule-y", dimensions: { - x: radius * 2.0, - y: height - (radius * 2.0), - z: radius * 2.0 + x: diameter, + y: height, + z: diameter } }, position: { x: 0, y: offset + height * 0.5, z: 0 }, diff --git a/server-console/src/main.js b/server-console/src/main.js index dc3fbd4333..5adb4be4cb 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -337,7 +337,17 @@ var notificationState = NotificationState.UNNOTIFIED; function setNotificationState (notificationType, pending = undefined) { if (pending !== undefined) { - pendingNotifications[notificationType] = pending; + if ((notificationType === HifiNotificationType.TRANSACTIONS || + notificationType === HifiNotificationType.ITEMS)) { + // special case, because we want to clear the indicator light + // on INVENTORY when either Transactions or Items are + // clicked on in the notification popup, we detect that case + // here and force both to be unnotified. + pendingNotifications[HifiNotificationType.TRANSACTIONS] = pending; + pendingNotifications[HifiNotificationType.ITEMS] = pending; + } else { + pendingNotifications[notificationType] = pending; + } notificationState = NotificationState.UNNOTIFIED; for (var key in pendingNotifications) { if (pendingNotifications[key]) { @@ -428,18 +438,12 @@ var labels = { setNotificationState(HifiNotificationType.PEOPLE, false); } }, - wallet: { - label: 'Wallet', + inventory: { + label: 'Inventory', click: function () { - StartInterface("hifiapp:WALLET"); - setNotificationState(HifiNotificationType.WALLET, false); - } - }, - marketplace: { - label: 'Market', - click: function () { - StartInterface("hifiapp:MARKET"); - setNotificationState(HifiNotificationType.MARKETPLACE, false); + StartInterface("hifiapp:INVENTORY"); + setNotificationState(HifiNotificationType.ITEMS, false); + setNotificationState(HifiNotificationType.TRANSACTIONS, false); } }, restart: { @@ -528,8 +532,7 @@ function buildMenuArray(serverState) { if (trayNotifications.enabled()) { menuArray.push(labels.goto); menuArray.push(labels.people); - menuArray.push(labels.wallet); - menuArray.push(labels.marketplace); + menuArray.push(labels.inventory); menuArray.push(separator); } menuArray.push(labels.showNotifications); @@ -565,8 +568,7 @@ function updateLabels(serverState) { labels.showNotifications.checked = trayNotifications.enabled(); labels.goto.icon = pendingNotifications[HifiNotificationType.GOTO] ? menuNotificationIcon : null; labels.people.icon = pendingNotifications[HifiNotificationType.PEOPLE] ? menuNotificationIcon : null; - labels.wallet.icon = pendingNotifications[HifiNotificationType.WALLET] ? menuNotificationIcon : null; - labels.marketplace.icon = pendingNotifications[HifiNotificationType.MARKETPLACE] ? menuNotificationIcon : null; + labels.inventory.icon = pendingNotifications[HifiNotificationType.ITEMS] || pendingNotifications[HifiNotificationType.TRANSACTIONS]? menuNotificationIcon : null; var onlineUsers = trayNotifications.getOnlineUsers(); delete labels.people.submenu; if (onlineUsers) { diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index 8a812625b4..1ddbd1d307 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -32,10 +32,10 @@ const StartInterface=hfApp.startInterface; const IsInterfaceRunning=hfApp.isInterfaceRunning; const NotificationType = { - GOTO: 'goto', - PEOPLE: 'people', - WALLET: 'wallet', - MARKETPLACE: 'marketplace' + GOTO: 'goto', + PEOPLE: 'people', + ITEMS: 'items', + TRANSACTIONS: 'transactions' }; @@ -89,34 +89,34 @@ HifiNotification.prototype = { } break; - case NotificationType.WALLET: + case NotificationType.TRANSACTIONS: if (typeof(this.data) === "number") { if (this.data === 1) { - text = "You have " + this.data + " unread Wallet transaction."; + text = "You have " + this.data + " unread transaction."; } else { - text = "You have " + this.data + " unread Wallet transactions."; + text = "You have " + this.data + " unread transactions."; } - message = "Click to open WALLET." - url = "hifiapp:hifi/commerce/wallet/Wallet.qml"; + message = "Click to open INVENTORY." + url = "hifiapp:INVENTORY"; break; } text = this.data.message.replace(/<\/?[^>]+(>|$)/g, ""); - message = "Click to open WALLET."; - url = "hifiapp:WALLET"; + message = "Click to open INVENTORY."; + url = "hifiapp:INVENTORY"; break; - case NotificationType.MARKETPLACE: + case NotificationType.ITEMS: if (typeof(this.data) === "number") { if (this.data === 1) { - text = this.data + " of your purchased items has an update available."; + text = this.data + " of your items has an update available."; } else { - text = this.data + " of your purchased items have updates available."; + text = this.data + " of your items have updates available."; } } else { text = "Update available for " + this.data.base_item_title + "."; } - message = "Click to open MARKET."; - url = "hifiapp:MARKET"; + message = "Click to open INVENTORY."; + url = "hifiapp:INVENTORY"; break; } notifier.notify({ @@ -235,7 +235,6 @@ HifiNotifications.prototype = { }, _showNotification: function () { var _this = this; - if (osType === 'Darwin') { this.pendingNotifications[0].show(function () { // For OSX @@ -325,10 +324,10 @@ HifiNotifications.prototype = { case NotificationType.PEOPLE: notifyData = content.data.users; break; - case NotificationType.WALLET: + case NotificationType.TRANSACTIONS: notifyData = content.data.history; break; - case NotificationType.MARKETPLACE: + case NotificationType.ITEMS: notifyData = content.data.updates; break; } @@ -376,19 +375,16 @@ HifiNotifications.prototype = { } }, function (error, data) { if (error || !data.body) { - console.log("Error: unable to get " + url); - finished(false); + console.log("Error: " + error + ": unable to get " + url); return; } var content = JSON.parse(data.body); if (!content || content.status != 'success') { console.log("Error: unable to get " + url); - finished(false); return; } if (!content.total_entries) { - finished(true, token); return; } if (!content.total_entries) { @@ -487,7 +483,7 @@ HifiNotifications.prototype = { console.log("Polling for economic activity"); var url = METAVERSE_SERVER_URL + ECONOMIC_ACTIVITY_URL + '?' + options.join('&'); console.log(url); - _this._pollCommon(NotificationType.WALLET, url, since, function () {}); + _this._pollCommon(NotificationType.TRANSACTIONS, url, since, function () {}); }, pollForMarketplaceUpdates: function (since) { var _this = this; @@ -499,7 +495,7 @@ HifiNotifications.prototype = { console.log("Polling for marketplace update"); var url = METAVERSE_SERVER_URL + UPDATES_URL + '?' + options.join('&'); console.log(url); - _this._pollCommon(NotificationType.MARKETPLACE, url, since, function (success, token) { + _this._pollCommon(NotificationType.ITEMS, url, since, function (success, token) { if (success) { var options = [ 'page=1', @@ -512,7 +508,7 @@ HifiNotifications.prototype = { 'bearer': token } }, function (error, data) { - _this._pollToDisableHighlight(NotificationType.MARKETPLACE, error, data); + _this._pollToDisableHighlight(NotificationType.ITEMS, error, data); }); } }); diff --git a/tools/nitpick/src/TestRunner.cpp b/tools/nitpick/src/TestRunner.cpp index 8667852032..c7823ba751 100644 --- a/tools/nitpick/src/TestRunner.cpp +++ b/tools/nitpick/src/TestRunner.cpp @@ -173,9 +173,9 @@ void TestRunner::setWorkingFolder() { } script.write("#!/bin/sh\n\n"); - script.write("echo resizing interface"); + script.write("echo resizing interface\n"); script.write(("osascript " + _workingFolder + "/setInterfaceSizeAndPosition.scpt\n").toStdString().c_str()); - script.write("echo resize complete"); + script.write("echo resize complete\n"); script.close(); script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); #endif