From 94902106e63acc1eda20354a9a42d439499b8afe Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Sun, 13 Nov 2016 12:17:12 -0800 Subject: [PATCH 1/4] use for loop for kick and mute overlay cleanup --- scripts/system/mod.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/system/mod.js b/scripts/system/mod.js index f94be98406..7e5cc5d2a5 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -137,10 +137,10 @@ function updateOverlays() { isFacingAvatar: true, drawInFront: true }); - + modOverlays[avatarID]=[newKickOverlay]; - - if (Users.canKick) { + + if (Users.canKick) { var newMuteOverlay = Overlays.addOverlay("image3d", { url: muteOverlayURL(), position: muteOverlayPosition, @@ -166,11 +166,11 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ if (isShowingOverlays) { // we are currently showing overlays and an avatar just went away - // first remove the rendered overlay - for (var overlay in modOverlays[avatarID]) { - Overlays.deleteOverlay(overlay); + // first remove the rendered overlays + for (var j = 0; j < modOverlays[avatarID].length; ++j) { + Overlays.deleteOverlay(modOverlays[avatarID][j]); } - + // delete the saved ID of the overlay from our mod overlays object delete modOverlays[avatarID]; } @@ -193,7 +193,7 @@ function handleSelectedOverlay(clickedOverlay) { Users.ignore(avatarID); } // cleanup of the overlay is handled by the connection to avatarRemovedEvent - + } else if (muteOverlay && clickedOverlay.overlayID == muteOverlay) { Users.mute(avatarID); } From 14857a085a4fb15ae43c3c54a76c10be64bb97d3 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 19 Nov 2016 15:05:53 -0800 Subject: [PATCH 2/4] personal space feature --- assignment-client/src/AvatarAudioTimer.cpp | 1 - assignment-client/src/audio/AudioMixer.cpp | 35 ++- assignment-client/src/audio/AudioMixer.h | 1 + .../src/audio/AudioMixerClientData.cpp | 4 - .../src/audio/AudioMixerClientData.h | 1 + assignment-client/src/avatars/AvatarMixer.cpp | 19 ++ assignment-client/src/avatars/AvatarMixer.h | 1 + .../src/avatars/AvatarMixerClientData.cpp | 13 + .../src/avatars/AvatarMixerClientData.h | 8 + interface/src/ui/PreferencesDialog.cpp | 14 +- libraries/networking/src/Node.cpp | 10 + libraries/networking/src/Node.h | 7 + libraries/networking/src/NodeList.cpp | 22 +- libraries/networking/src/NodeList.h | 13 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- .../src/UsersScriptingInterface.cpp | 24 ++ .../src/UsersScriptingInterface.h | 66 +++++ scripts/defaultScripts.js | 3 +- scripts/system/assets/images/tools/bubble.svg | 275 ++++++++++++++++++ scripts/system/bubble.js | 58 ++++ 20 files changed, 557 insertions(+), 21 deletions(-) create mode 100644 scripts/system/assets/images/tools/bubble.svg create mode 100644 scripts/system/bubble.js diff --git a/assignment-client/src/AvatarAudioTimer.cpp b/assignment-client/src/AvatarAudioTimer.cpp index 77dd61043e..d031b9d9f6 100644 --- a/assignment-client/src/AvatarAudioTimer.cpp +++ b/assignment-client/src/AvatarAudioTimer.cpp @@ -15,7 +15,6 @@ // this should send a signal every 10ms, with pretty good precision. Hardcoding // to 10ms since that's what you'd want for audio. void AvatarAudioTimer::start() { - qDebug() << __FUNCTION__; auto startTime = usecTimestampNow(); quint64 frameCounter = 0; const int TARGET_INTERVAL_USEC = 10000; // 10ms diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index ffd7cc703b..3dba1ce1c2 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -95,7 +95,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); - + packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); } @@ -393,16 +393,26 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) { && !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); - // enumerate the ARBs attached to the otherNode and add all that should be added to mix - auto streamsCopy = otherNodeClientData->getAudioStreams(); + // check to see if we're ignoring in radius + bool insideIgnoreRadius = false; + if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { + AudioMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); + AudioMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius()); + if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) { + insideIgnoreRadius = true; + } + } - for (auto& streamPair : streamsCopy) { - - auto otherNodeStream = streamPair.second; - - if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) { - addStreamToMixForListeningNodeWithStream(*listenerNodeData, *otherNodeStream, otherNode->getUUID(), - *nodeAudioStream); + if (!insideIgnoreRadius) { + // enumerate the ARBs attached to the otherNode and add all that should be added to mix + auto streamsCopy = otherNodeClientData->getAudioStreams(); + for (auto& streamPair : streamsCopy) { + auto otherNodeStream = streamPair.second; + if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) { + addStreamToMixForListeningNodeWithStream(*listenerNodeData, *otherNodeStream, otherNode->getUUID(), + *nodeAudioStream); + } } } } @@ -634,11 +644,14 @@ void AudioMixer::handleKillAvatarPacket(QSharedPointer packet, } } - void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { sendingNode->parseIgnoreRequestMessage(packet); } +void AudioMixer::handleRadiusIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + sendingNode->parseIgnoreRadiusRequestMessage(packet); +} + void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) { auto injectorClientData = qobject_cast(sender()); if (injectorClientData) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 91eafadd9d..9bf337fe60 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -48,6 +48,7 @@ private slots: void handleNegotiateAudioFormat(QSharedPointer message, SharedNodePointer sendingNode); void handleNodeKilled(SharedNodePointer killedNode); void handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleRadiusIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleNodeMuteRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 5b8c4aa105..70d6a67b5b 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -365,10 +365,6 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() { } void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { - qDebug() << __FUNCTION__ << - "sendingNode:" << *node << - "currentCodec:" << currentCodec << - "receivedCodec:" << recievedCodec; sendSelectAudioFormat(node, currentCodec); } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index c74461a444..a8b6b6606d 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -89,6 +89,7 @@ public: bool shouldMuteClient() { return _shouldMuteClient; } void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; } + glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); } signals: void injectorStreamFinished(const QUuid& streamIdentifier); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 2c9fadc7b1..63cda4a4ff 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -46,6 +46,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); + packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); @@ -237,6 +238,20 @@ void AvatarMixer::broadcastAvatarData() { || otherNode->isIgnoringNodeWithID(node->getUUID())) { return false; } else { + AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + // check to see if we're ignoring in radius + if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { + float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius()); + if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) { + nodeData->ignoreOther(node, otherNode); + otherData->ignoreOther(otherNode, node); + return false; + } + } + // not close enough to ignore + nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID()); + otherData->removeFromRadiusIgnoringSet(node->getUUID()); return true; } }, @@ -442,6 +457,10 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer senderNode->parseIgnoreRequestMessage(message); } +void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + sendingNode->parseIgnoreRadiusRequestMessage(packet); +} + void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 6e1d722145..f537cc9244 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -38,6 +38,7 @@ private slots: void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); void handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleRadiusIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 4b7a696d58..60d03f8930 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -11,6 +11,9 @@ #include +#include +#include + #include "AvatarMixerClientData.h" int AvatarMixerClientData::parseData(ReceivedMessage& message) { @@ -39,6 +42,16 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } } +void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { + if (!isRadiusIgnoring(other->getUUID())) { + addToRadiusIgnoringSet(other->getUUID()); + auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID); + killPacket->write(other->getUUID().toRfc4122()); + DependencyManager::get()->sendUnreliablePacket(*killPacket, *self); + _hasReceivedFirstPacketsFrom.erase(other->getUUID()); + } +} + void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["display_name"] = _avatar->getDisplayName(); jsonObject["full_rate_distance"] = _fullRateDistance; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 4a816291f4..96bc275a13 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -79,6 +79,13 @@ public: { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } void loadJSONStats(QJsonObject& jsonObject) const; + + glm::vec3 getPosition() { return _avatar ? _avatar->getPosition() : glm::vec3(0); } + bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } + void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } + void removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); } + void ignoreOther(SharedNodePointer self, SharedNodePointer other); + private: AvatarSharedPointer _avatar { new AvatarData() }; @@ -99,6 +106,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; + std::unordered_set _radiusIgnoredOthers; }; #endif // hifi_AvatarMixerClientData_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 35af1067d8..dea1c49346 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -32,7 +32,7 @@ void setupPreferences() { auto preferences = DependencyManager::get(); - + auto nodeList = DependencyManager::get(); auto myAvatar = DependencyManager::get()->getMyAvatar(); static const QString AVATAR_BASICS { "Avatar Basics" }; { @@ -68,6 +68,18 @@ void setupPreferences() { auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); } + { + auto getter = [=]()->float { return nodeList->getIgnoreRadius(); }; + auto setter = [=](float value) { + nodeList->ignoreNodesInRadius(value, nodeList->getIgnoreRadiusEnabled()); + }; + auto preference = new SpinnerPreference(AVATAR_BASICS, "Personal space bubble radius (default is 1m)", getter, setter); + preference->setMin(0.01f); + preference->setMax(99.9f); + preference->setDecimals(2); + preference->setStep(0.25); + preferences->addPreference(preference); + } // UI { diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 406498b025..36e7cc961b 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -64,6 +64,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, { // Update socket's object name setType(_type); + _ignoreRadiusEnabled = false; } void Node::setType(char type) { @@ -101,6 +102,15 @@ void Node::addIgnoredNode(const QUuid& otherNodeID) { } } +void Node::parseIgnoreRadiusRequestMessage(QSharedPointer message) { + bool enabled; + float radius; + message->readPrimitive(&enabled); + message->readPrimitive(&radius); + _ignoreRadiusEnabled = enabled; + _ignoreRadius = radius; +} + QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 18088c6cea..ab8cdb3a41 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -74,10 +74,14 @@ public: void parseIgnoreRequestMessage(QSharedPointer message); void addIgnoredNode(const QUuid& otherNodeID); bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); } + void parseIgnoreRadiusRequestMessage(QSharedPointer message); friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); + bool isIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled; } + float getIgnoreRadius() { return _ignoreRadiusEnabled ? _ignoreRadius.load() : std::numeric_limits::max(); } + private: // privatize copy and assignment operator to disallow Node copying Node(const Node &otherNode); @@ -94,6 +98,9 @@ private: MovingPercentile _clockSkewMovingPercentile; NodePermissions _permissions; tbb::concurrent_unordered_set _ignoredNodeIDSet; + + std::atomic_bool _ignoreRadiusEnabled; + std::atomic _ignoreRadius { 0.0f }; }; Q_DECLARE_METATYPE(Node*) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 361070b306..86b9bc1794 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -750,9 +750,26 @@ bool NodeList::sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr) { return _domainHandler.getSockAddr() == sockAddr || LimitedNodeList::sockAddrBelongsToNode(sockAddr); } +void NodeList::ignoreNodesInRadius(float radiusToIgnore, bool enabled) { + _ignoreRadiusEnabled.set(enabled); + _ignoreRadius.set(radiusToIgnore); + + eachMatchingNode([](const SharedNodePointer& node)->bool { + return (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer); + }, [this](const SharedNodePointer& destinationNode) { + sendIgnoreRadiusStateToNode(destinationNode); + }); +} + +void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode) { + auto ignorePacket = NLPacket::create(PacketType::RadiusIgnoreRequest, sizeof(bool) + sizeof(float), true); + ignorePacket->writePrimitive(_ignoreRadiusEnabled.get()); + ignorePacket->writePrimitive(_ignoreRadius.get()); + sendPacket(std::move(ignorePacket), *destinationNode); +} + void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) { // enumerate the nodes to send a reliable ignore packet to each that can leverage it - if (!nodeID.isNull() && _sessionUUID != nodeID) { eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool { if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) { @@ -811,6 +828,9 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) { // send this NLPacketList to the new node sendPacketList(std::move(ignorePacketList), *newNode); } + + // also send them the current ignore radius state. + sendIgnoreRadiusStateToNode(newNode); } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 4c06a13469..f30283f3c2 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -30,6 +30,7 @@ #include #include +#include #include "DomainHandler.h" #include "LimitedNodeList.h" @@ -70,6 +71,12 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } + void ignoreNodesInRadius(float radiusToIgnore, bool enabled = true); + float getIgnoreRadius() const { return _ignoreRadius.get(); } + bool getIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled.get(); } + void toggleIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), !getIgnoreRadiusEnabled()); } + void enableIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), true); } + void disableIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), false); } void ignoreNodeBySessionID(const QUuid& nodeID); bool isIgnoringNode(const QUuid& nodeID) const; @@ -101,7 +108,7 @@ signals: void limitOfSilentDomainCheckInsReached(); void receivedDomainServerList(); void ignoredNode(const QUuid& nodeID); - + private slots: void stopKeepalivePingTimer(); void sendPendingDSPathQuery(); @@ -146,6 +153,10 @@ private: mutable QReadWriteLock _ignoredSetLock; tbb::concurrent_unordered_set _ignoredNodeIDs; + void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode); + Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; + Setting::Handle _ignoreRadius { "IgnoreRadius", 1.0f }; + #if (PR_BUILD || DEV_BUILD) bool _shouldSendNewerVersion { false }; #endif diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 8d63b972cc..2b17aa7d57 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -100,7 +100,8 @@ public: MoreEntityShapes, NodeKickRequest, NodeMuteRequest, - LAST_PACKET_TYPE = NodeMuteRequest + RadiusIgnoreRequest, + LAST_PACKET_TYPE = RadiusIgnoreRequest }; }; diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index 702368c2b3..c809617995 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -38,3 +38,27 @@ bool UsersScriptingInterface::getCanKick() { // ask the NodeList to return our ability to kick return DependencyManager::get()->getThisNodeCanKick(); } + +void UsersScriptingInterface::toggleIgnoreRadius() { + DependencyManager::get()->toggleIgnoreRadius(); +} + +void UsersScriptingInterface::enableIgnoreRadius() { + DependencyManager::get()->enableIgnoreRadius(); +} + +void UsersScriptingInterface::disableIgnoreRadius() { + DependencyManager::get()->disableIgnoreRadius(); +} + +void UsersScriptingInterface::setIgnoreRadius(float radius, bool enabled) { + DependencyManager::get()->ignoreNodesInRadius(radius, enabled); +} + + float UsersScriptingInterface::getIgnoreRadius() { + return DependencyManager::get()->getIgnoreRadius(); +} + +bool UsersScriptingInterface::getIgnoreRadiusEnabled() { + return DependencyManager::get()->getIgnoreRadiusEnabled(); +} diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 3c98d0a393..07398558e5 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -16,6 +16,9 @@ #include +/**jsdoc +* @namespace Users +*/ class UsersScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -26,12 +29,75 @@ public: UsersScriptingInterface(); public slots: + + /**jsdoc + * Ignore another user. + * @function Users.ignore + * @param {nodeID} nodeID The node or session ID of the user you want to ignore. + */ void ignore(const QUuid& nodeID); + + /**jsdoc + * Kick another user. + * @function Users.kick + * @param {nodeID} nodeID The node or session ID of the user you want to kick. + */ void kick(const QUuid& nodeID); + + /**jsdoc + * Mute another user. + * @function Users.mute + * @param {nodeID} nodeID The node or session ID of the user you want to mute. + */ void mute(const QUuid& nodeID); + /**jsdoc + * Returns `true` if the DomainServer will allow this Node/Avatar to make kick + * @function Users.getCanKick + * @return {bool} `true` if the client can kick other users, `false` if not. + */ bool getCanKick(); + /**jsdoc + * Toggle the state of the ignore in radius feature + * @function Users.toggleIgnoreRadius + */ + void toggleIgnoreRadius(); + + /**jsdoc + * Enables the ignore radius feature. + * @function Users.enableIgnoreRadius + */ + void enableIgnoreRadius(); + + /**jsdoc + * Disables the ignore radius feature. + * @function Users.disableIgnoreRadius + */ + void disableIgnoreRadius(); + + /**jsdoc + * sets the parameters for the ignore radius feature. + * @function Users.setIgnoreRadius + * @param {number} radius The radius for the auto ignore in radius feature + * @param {bool} [enabled=true] Whether the ignore in radius feature should be enabled + */ + void setIgnoreRadius(float radius, bool enabled = true); + + /**jsdoc + * Returns the effective radius of the ingore radius feature if it is enabled. + * @function Users.getIgnoreRadius + * @return {number} radius of the ignore feature + */ + float getIgnoreRadius(); + + /**jsdoc + * Returns `true` if the ignore in radius feature is enabled + * @function Users.getIgnoreRadiusEnabled + * @return {bool} `true` if the ignore in radius feature is enabled, `false` if not. + */ + bool getIgnoreRadiusEnabled(); + signals: void canKickChanged(bool canKick); }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 4376960ea5..90a77b508d 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -33,7 +33,8 @@ var DEFAULT_SCRIPTS = [ "system/dialTone.js", "system/firstPersonHMD.js", "system/snapshot.js", - "system/help.js" + "system/help.js", + "system/bubble.js" ]; // add a menu item for debugging diff --git a/scripts/system/assets/images/tools/bubble.svg b/scripts/system/assets/images/tools/bubble.svg new file mode 100644 index 0000000000..064b7734a9 --- /dev/null +++ b/scripts/system/assets/images/tools/bubble.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js new file mode 100644 index 0000000000..ba317ecdca --- /dev/null +++ b/scripts/system/bubble.js @@ -0,0 +1,58 @@ +"use strict"; + +// +// bubble.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 11/18/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ + + +(function() { // BEGIN LOCAL_SCOPE + +// grab the toolbar +var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + +var ASSETS_PATH = Script.resolvePath("assets"); +var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); + +function buttonImageURL() { + return TOOLS_PATH + 'bubble.svg'; +} + +var bubbleActive = Users.getIgnoreRadiusEnabled(); + +// setup the mod button and add it to the toolbar +var button = toolbar.addButton({ + objectName: 'bubble', + imageURL: buttonImageURL(), + visible: true, + buttonState: bubbleActive ? 0 : 1, + defaultState: bubbleActive ? 0 : 1, + hoverState: bubbleActive ? 2 : 3, + alpha: 0.9 +}); + + +// handle clicks on the toolbar button +function buttonClicked(){ + Users.toggleIgnoreRadius(); + bubbleActive = Users.getIgnoreRadiusEnabled(); + button.writeProperty('buttonState', bubbleActive ? 0 : 1); + button.writeProperty('defaultState', bubbleActive ? 0 : 1); + button.writeProperty('hoverState', bubbleActive ? 2 : 3); +} + +button.clicked.connect(buttonClicked); + +// cleanup the toolbar button and overlays when script is stopped +Script.scriptEnding.connect(function() { + toolbar.removeButton('bubble'); +}); + +}()); // END LOCAL_SCOPE From d69d77d0eed12bc939e667bca0677a12de9d58ae Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 21 Nov 2016 21:36:17 -0800 Subject: [PATCH 3/4] Refactor the audio device format detection. Only use the native format when Qt can correctly determine it. --- libraries/audio-client/src/AudioClient.cpp | 63 +++++++++++++--------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a05d550fd8..84c64398d3 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -375,41 +375,53 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice(); } +// attempt to use the native sample rate and channel count +bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, + QAudioFormat& audioFormat) { + + audioFormat = audioDevice.preferredFormat(); + + audioFormat.setCodec("audio/pcm"); + audioFormat.setSampleSize(16); + audioFormat.setSampleType(QAudioFormat::SignedInt); + audioFormat.setByteOrder(QAudioFormat::LittleEndian); + + if (!audioDevice.isFormatSupported(audioFormat)) { + qCDebug(audioclient) << "WARNING: The native format is" << audioFormat << "but isFormatSupported() failed."; + return false; + } + // converting to/from this rate must produce an integral number of samples + if (audioFormat.sampleRate() * AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL % AudioConstants::SAMPLE_RATE != 0) { + qCDebug(audioclient) << "WARNING: The native sample rate [" << audioFormat.sampleRate() << "] is not supported."; + return false; + } + return true; +} + bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, const QAudioFormat& desiredAudioFormat, QAudioFormat& adjustedAudioFormat) { qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - adjustedAudioFormat = desiredAudioFormat; +#if defined(Q_OS_ANDROID) || defined(Q_OS_OSX) + // As of Qt5.6, Android returns the native OpenSLES sample rate when possible, else 48000 + // Mac OSX returns the preferred CoreAudio format + if (nativeFormatForAudioDevice(audioDevice, adjustedAudioFormat)) { + return true; + } +#endif #if defined(Q_OS_WIN) - - // On Windows, using WASAPI shared mode, the sample rate and channel count must - // exactly match the internal mix format. Any other format will fail to open. - - adjustedAudioFormat = audioDevice.preferredFormat(); // returns mixFormat - - adjustedAudioFormat.setCodec("audio/pcm"); - adjustedAudioFormat.setSampleSize(16); - adjustedAudioFormat.setSampleType(QAudioFormat::SignedInt); - adjustedAudioFormat.setByteOrder(QAudioFormat::LittleEndian); - - if (!audioDevice.isFormatSupported(adjustedAudioFormat)) { - qCDebug(audioclient) << "WARNING: The mix format is" << adjustedAudioFormat << "but isFormatSupported() failed."; - return false; + if (IsWindows8OrGreater()) { + // On Windows using WASAPI shared-mode, returns the internal mix format + if (nativeFormatForAudioDevice(audioDevice, adjustedAudioFormat)) { + return true; + } } - // converting to/from this rate must produce an integral number of samples - if (adjustedAudioFormat.sampleRate() * AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL % AudioConstants::SAMPLE_RATE != 0) { - qCDebug(audioclient) << "WARNING: The current sample rate [" << adjustedAudioFormat.sampleRate() << "] is not supported."; - return false; - } - return true; +#endif -#elif defined(Q_OS_ANDROID) - // FIXME: query the native sample rate of the device? - adjustedAudioFormat.setSampleRate(48000); -#else + adjustedAudioFormat = desiredAudioFormat; // // Attempt the device sample rate in decreasing order of preference. @@ -433,7 +445,6 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, } else if (audioDevice.supportedSampleRates().contains(176400)) { adjustedAudioFormat.setSampleRate(176400); } -#endif if (adjustedAudioFormat != desiredAudioFormat) { // return the nearest in case it needs 2 channels From 3d0b557af6e866c8b3d02424ba9d236b2edd8cf0 Mon Sep 17 00:00:00 2001 From: samcake Date: Tue, 22 Nov 2016 13:01:02 -0800 Subject: [PATCH 4/4] Fix the bad binding for polyline --- .../src/RenderablePolyLineEntityItem.cpp | 15 +++++++++------ .../src/RenderablePolyLineEntityItem.h | 4 +++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index dc2545b956..286efb5fb8 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -46,7 +46,8 @@ _numVertices(0) gpu::PipelinePointer RenderablePolyLineEntityItem::_pipeline; gpu::Stream::FormatPointer RenderablePolyLineEntityItem::_format; -int32_t RenderablePolyLineEntityItem::PAINTSTROKE_GPU_SLOT; +const int32_t RenderablePolyLineEntityItem::PAINTSTROKE_TEXTURE_SLOT; +const int32_t RenderablePolyLineEntityItem::PAINTSTROKE_UNIFORM_SLOT; void RenderablePolyLineEntityItem::createPipeline() { static const int NORMAL_OFFSET = 12; @@ -62,8 +63,8 @@ void RenderablePolyLineEntityItem::createPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); gpu::Shader::BindingSet slotBindings; - PAINTSTROKE_GPU_SLOT = 0; - slotBindings.insert(gpu::Shader::Binding(std::string("paintStrokeTextureBinding"), PAINTSTROKE_GPU_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -193,14 +194,14 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) { Transform transform = Transform(); transform.setTranslation(getPosition()); transform.setRotation(getRotation()); - batch.setUniformBuffer(0, _uniformBuffer); + batch.setUniformBuffer(PAINTSTROKE_UNIFORM_SLOT, _uniformBuffer); batch.setModelTransform(transform); batch.setPipeline(_pipeline); if (_texture->isLoaded()) { - batch.setResourceTexture(PAINTSTROKE_GPU_SLOT, _texture->getGPUTexture()); + batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, _texture->getGPUTexture()); } else { - batch.setResourceTexture(PAINTSTROKE_GPU_SLOT, args->_whiteTexture); + batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, args->_whiteTexture); } batch.setInputFormat(_format); @@ -208,6 +209,8 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) { if (_isFading) { batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime)); + } else { + batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); } batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 75b2bcd58a..44b29bdec1 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -40,7 +40,9 @@ public: static gpu::PipelinePointer _pipeline; static gpu::Stream::FormatPointer _format; - static int32_t PAINTSTROKE_GPU_SLOT; + + static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; + static const int32_t PAINTSTROKE_UNIFORM_SLOT { 0 }; protected: void updateGeometry();