Merge branch 'master' of https://github.com/highfidelity/hifi into location-cleanup

This commit is contained in:
howard-stearns 2016-11-21 11:47:30 -08:00
commit 5b216578d7
39 changed files with 921 additions and 260 deletions

View file

@ -15,7 +15,6 @@
// this should send a signal every 10ms, with pretty good precision. Hardcoding // this should send a signal every 10ms, with pretty good precision. Hardcoding
// to 10ms since that's what you'd want for audio. // to 10ms since that's what you'd want for audio.
void AvatarAudioTimer::start() { void AvatarAudioTimer::start() {
qDebug() << __FUNCTION__;
auto startTime = usecTimestampNow(); auto startTime = usecTimestampNow();
quint64 frameCounter = 0; quint64 frameCounter = 0;
const int TARGET_INTERVAL_USEC = 10000; // 10ms const int TARGET_INTERVAL_USEC = 10000; // 10ms

View file

@ -95,7 +95,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); 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())) { && !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) {
AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData();
// enumerate the ARBs attached to the otherNode and add all that should be added to mix // check to see if we're ignoring in radius
auto streamsCopy = otherNodeClientData->getAudioStreams(); bool insideIgnoreRadius = false;
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
AudioMixerClientData* otherData = reinterpret_cast<AudioMixerClientData*>(otherNode->getLinkedData());
AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData());
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius());
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) {
insideIgnoreRadius = true;
}
}
for (auto& streamPair : streamsCopy) { if (!insideIgnoreRadius) {
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
auto otherNodeStream = streamPair.second; auto streamsCopy = otherNodeClientData->getAudioStreams();
for (auto& streamPair : streamsCopy) {
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) { auto otherNodeStream = streamPair.second;
addStreamToMixForListeningNodeWithStream(*listenerNodeData, *otherNodeStream, otherNode->getUUID(), if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
*nodeAudioStream); addStreamToMixForListeningNodeWithStream(*listenerNodeData, *otherNodeStream, otherNode->getUUID(),
*nodeAudioStream);
}
} }
} }
} }
@ -634,11 +644,14 @@ void AudioMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet,
} }
} }
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) { void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRequestMessage(packet); sendingNode->parseIgnoreRequestMessage(packet);
} }
void AudioMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRadiusRequestMessage(packet);
}
void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) { void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender()); auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender());
if (injectorClientData) { if (injectorClientData) {

View file

@ -48,6 +48,7 @@ private slots:
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void handleNodeKilled(SharedNodePointer killedNode); void handleNodeKilled(SharedNodePointer killedNode);
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);

View file

@ -365,10 +365,6 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
} }
void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
qDebug() << __FUNCTION__ <<
"sendingNode:" << *node <<
"currentCodec:" << currentCodec <<
"receivedCodec:" << recievedCodec;
sendSelectAudioFormat(node, currentCodec); sendSelectAudioFormat(node, currentCodec);
} }

View file

@ -89,6 +89,7 @@ public:
bool shouldMuteClient() { return _shouldMuteClient; } bool shouldMuteClient() { return _shouldMuteClient; }
void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; } void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; }
glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); }
signals: signals:
void injectorStreamFinished(const QUuid& streamIdentifier); void injectorStreamFinished(const QUuid& streamIdentifier);

View file

@ -46,6 +46,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);
@ -237,6 +238,20 @@ void AvatarMixer::broadcastAvatarData() {
|| otherNode->isIgnoringNodeWithID(node->getUUID())) { || otherNode->isIgnoringNodeWithID(node->getUUID())) {
return false; return false;
} else { } else {
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(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; return true;
} }
}, },
@ -442,6 +457,10 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
senderNode->parseIgnoreRequestMessage(message); senderNode->parseIgnoreRequestMessage(message);
} }
void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRadiusRequestMessage(packet);
}
void AvatarMixer::sendStatsPacket() { void AvatarMixer::sendStatsPacket() {
QJsonObject statsObject; QJsonObject statsObject;
statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames;

View file

@ -38,6 +38,7 @@ private slots:
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message); void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void domainSettingsRequestComplete(); void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);

View file

@ -11,6 +11,9 @@
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include <DependencyManager.h>
#include <NodeList.h>
#include "AvatarMixerClientData.h" #include "AvatarMixerClientData.h"
int AvatarMixerClientData::parseData(ReceivedMessage& message) { 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<NodeList>()->sendUnreliablePacket(*killPacket, *self);
_hasReceivedFirstPacketsFrom.erase(other->getUUID());
}
}
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
jsonObject["display_name"] = _avatar->getDisplayName(); jsonObject["display_name"] = _avatar->getDisplayName();
jsonObject["full_rate_distance"] = _fullRateDistance; jsonObject["full_rate_distance"] = _fullRateDistance;

View file

@ -79,6 +79,13 @@ public:
{ return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; }
void loadJSONStats(QJsonObject& jsonObject) const; 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: private:
AvatarSharedPointer _avatar { new AvatarData() }; AvatarSharedPointer _avatar { new AvatarData() };
@ -99,6 +106,7 @@ private:
int _numOutOfOrderSends = 0; int _numOutOfOrderSends = 0;
SimpleMovingAverage _avgOtherAvatarDataRate; SimpleMovingAverage _avgOtherAvatarDataRate;
std::unordered_set<QUuid> _radiusIgnoredOthers;
}; };
#endif // hifi_AvatarMixerClientData_h #endif // hifi_AvatarMixerClientData_h

View file

@ -684,6 +684,79 @@
} }
] ]
}, },
{
"name": "permissions",
"type": "table",
"caption": "Permissions for Specific Users",
"can_add_new_rows": true,
"groups": [
{
"label": "User",
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 7
}
],
"columns": [
{
"name": "permissions_id",
"label": ""
},
{
"name": "id_can_connect",
"label": "Connect",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez",
"label": "Rez",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp",
"label": "Rez Temporary",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_connect_past_max_capacity",
"label": "Ignore Max Capacity",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
{ {
"name": "ip_permissions", "name": "ip_permissions",
"type": "table", "type": "table",
@ -757,18 +830,17 @@
] ]
}, },
{ {
"name": "permissions", "name": "mac_permissions",
"type": "table", "type": "table",
"caption": "Permissions for Specific Users", "caption": "Permissions for Users with MAC Addresses",
"can_add_new_rows": true, "can_add_new_rows": true,
"groups": [ "groups": [
{ {
"label": "User", "label": "MAC Address",
"span": 1 "span": 1
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 7 "span": 7
} }
], ],

View file

@ -119,15 +119,20 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
nodeData->setNodeInterestSet(safeInterestSet); nodeData->setNodeInterestSet(safeInterestSet);
nodeData->setPlaceName(nodeConnection.placeName); nodeData->setPlaceName(nodeConnection.placeName);
qDebug() << "Allowed connection from node" << uuidStringWithoutCurlyBraces(node->getUUID())
<< "on" << message->getSenderSockAddr() << "with MAC" << nodeConnection.hardwareAddress;
// signal that we just connected a node so the DomainServer can get it a list // signal that we just connected a node so the DomainServer can get it a list
// and broadcast its presence right away // and broadcast its presence right away
emit connectedNode(node); emit connectedNode(node);
} else { } else {
qDebug() << "Refusing connection from node at" << message->getSenderSockAddr(); qDebug() << "Refusing connection from node at" << message->getSenderSockAddr()
<< "with hardware address" << nodeConnection.hardwareAddress;
} }
} }
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress) { NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
const QHostAddress& senderAddress, const QString& hardwareAddress) {
NodePermissions userPerms; NodePermissions userPerms;
userPerms.setAll(false); userPerms.setAll(false);
@ -144,8 +149,14 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms; qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
#endif #endif
if (!hardwareAddress.isEmpty() && _server->_settingsManager.hasPermissionsForMAC(hardwareAddress)) {
// this user comes from a MAC we have in our permissions table, apply those permissions
userPerms = _server->_settingsManager.getPermissionsForMAC(hardwareAddress);
if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) { #ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific MAC matches, so:" << userPerms;
#endif
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
// this user comes from an IP we have in our permissions table, apply those permissions // this user comes from an IP we have in our permissions table, apply those permissions
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress); userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
@ -158,6 +169,13 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific user matches, so:" << userPerms; qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
#endif
} else if (!hardwareAddress.isEmpty() && _server->_settingsManager.hasPermissionsForMAC(hardwareAddress)) {
// this user comes from a MAC we have in our permissions table, apply those permissions
userPerms = _server->_settingsManager.getPermissionsForMAC(hardwareAddress);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific MAC matches, so:" << userPerms;
#endif #endif
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) { } else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
// this user comes from an IP we have in our permissions table, apply those permissions // this user comes from an IP we have in our permissions table, apply those permissions
@ -255,7 +273,14 @@ void DomainGatekeeper::updateNodePermissions() {
// or the public socket if we haven't activated a socket for the node yet // or the public socket if we haven't activated a socket for the node yet
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket(); HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress()); QString hardwareAddress;
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
if (nodeData) {
hardwareAddress = nodeData->getHardwareAddress();
}
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress);
} }
node->setPermissions(userPerms); node->setPermissions(userPerms);
@ -308,6 +333,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
nodeData->setWalletUUID(it->second.getWalletUUID()); nodeData->setWalletUUID(it->second.getWalletUUID());
nodeData->setNodeVersion(it->second.getNodeVersion()); nodeData->setNodeVersion(it->second.getNodeVersion());
nodeData->setHardwareAddress(nodeConnection.hardwareAddress);
nodeData->setWasAssigned(true); nodeData->setWasAssigned(true);
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting // cleanup the PendingAssignedNodeData for this assignment now that it's connecting
@ -369,7 +395,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
} }
} }
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress()); userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(),
nodeConnection.hardwareAddress);
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
@ -425,6 +452,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
// if we have a username from the connect request, set it on the DomainServerNodeData // if we have a username from the connect request, set it on the DomainServerNodeData
nodeData->setUsername(username); nodeData->setUsername(username);
// set the hardware address passed in the connect request
nodeData->setHardwareAddress(nodeConnection.hardwareAddress);
// also add an interpolation to DomainServerNodeData so that servers can get username in stats // also add an interpolation to DomainServerNodeData so that servers can get username in stats
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
uuidStringWithoutCurlyBraces(newNode->getUUID()), username); uuidStringWithoutCurlyBraces(newNode->getUUID()), username);

View file

@ -107,7 +107,8 @@ private:
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress); NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
const QHostAddress& senderAddress, const QString& hardwareAddress);
void getGroupMemberships(const QString& username); void getGroupMemberships(const QString& username);
// void getIsGroupMember(const QString& username, const QUuid groupID); // void getIsGroupMember(const QString& username, const QUuid groupID);

View file

@ -53,6 +53,9 @@ public:
void setNodeVersion(const QString& nodeVersion) { _nodeVersion = nodeVersion; } void setNodeVersion(const QString& nodeVersion) { _nodeVersion = nodeVersion; }
const QString& getNodeVersion() { return _nodeVersion; } const QString& getNodeVersion() { return _nodeVersion; }
void setHardwareAddress(const QString& hardwareAddress) { _hardwareAddress = hardwareAddress; }
const QString& getHardwareAddress() { return _hardwareAddress; }
void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue); void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue);
void removeOverrideForKey(const QString& key, const QString& value); void removeOverrideForKey(const QString& key, const QString& value);
@ -81,6 +84,7 @@ private:
bool _isAuthenticated = true; bool _isAuthenticated = true;
NodeSet _nodeInterestSet; NodeSet _nodeInterestSet;
QString _nodeVersion; QString _nodeVersion;
QString _hardwareAddress;
QString _placeName; QString _placeName;

View file

@ -29,6 +29,8 @@
#include <NLPacketList.h> #include <NLPacketList.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include "DomainServerNodeData.h"
#include "DomainServerSettingsManager.h" #include "DomainServerSettingsManager.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
@ -439,6 +441,9 @@ void DomainServerSettingsManager::packPermissions() {
// save settings for IP addresses // save settings for IP addresses
packPermissionsForMap("permissions", _ipPermissions, IP_PERMISSIONS_KEYPATH); packPermissionsForMap("permissions", _ipPermissions, IP_PERMISSIONS_KEYPATH);
// save settings for MAC addresses
packPermissionsForMap("permissions", _macPermissions, MAC_PERMISSIONS_KEYPATH);
// save settings for groups // save settings for groups
packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH); packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH);
@ -506,6 +511,17 @@ void DomainServerSettingsManager::unpackPermissions() {
} }
}); });
needPack |= unpackPermissionsForKeypath(MAC_PERMISSIONS_KEYPATH, &_macPermissions,
[&](NodePermissionsPointer perms){
// make sure that this permission row is for a non-empty hardware
if (perms->getKey().first.isEmpty()) {
_macPermissions.remove(perms->getKey());
// we removed a row from the MAC permissions, we'll need a re-pack
needPack = true;
}
});
needPack |= unpackPermissionsForKeypath(GROUP_PERMISSIONS_KEYPATH, &_groupPermissions, needPack |= unpackPermissionsForKeypath(GROUP_PERMISSIONS_KEYPATH, &_groupPermissions,
[&](NodePermissionsPointer perms){ [&](NodePermissionsPointer perms){
@ -558,7 +574,8 @@ void DomainServerSettingsManager::unpackPermissions() {
qDebug() << "--------------- permissions ---------------------"; qDebug() << "--------------- permissions ---------------------";
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets; QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
<< _groupPermissions.get() << _groupForbiddens.get() << _ipPermissions.get(); << _groupPermissions.get() << _groupForbiddens.get()
<< _ipPermissions.get() << _macPermissions.get();
foreach (auto permissionSet, permissionsSets) { foreach (auto permissionSet, permissionsSets) {
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet); QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet);
while (i.hasNext()) { while (i.hasNext()) {
@ -653,19 +670,25 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
auto verifiedUsername = matchingNode->getPermissions().getVerifiedUserName(); auto verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
bool hadExistingPermissions = false; bool newPermissions = false;
if (!verifiedUsername.isEmpty()) { if (!verifiedUsername.isEmpty()) {
// if we have a verified user name for this user, we apply the kick to the username // if we have a verified user name for this user, we apply the kick to the username
// check if there were already permissions // check if there were already permissions
hadExistingPermissions = havePermissionsForName(verifiedUsername); bool hadPermissions = havePermissionsForName(verifiedUsername);
// grab or create permissions for the given username // grab or create permissions for the given username
destinationPermissions = _agentPermissions[matchingNode->getPermissions().getKey()]; auto userPermissions = _agentPermissions[matchingNode->getPermissions().getKey()];
newPermissions = !hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain);
// ensure that the connect permission is clear
userPermissions->clear(NodePermissions::Permission::canConnectToDomain);
} else { } else {
// otherwise we apply the kick to the IP from active socket for this node // otherwise we apply the kick to the IP from active socket for this node and the MAC address
// (falling back to the public socket if not yet active)
// remove connect permissions for the IP (falling back to the public socket if not yet active)
auto& kickAddress = matchingNode->getActiveSocket() auto& kickAddress = matchingNode->getActiveSocket()
? matchingNode->getActiveSocket()->getAddress() ? matchingNode->getActiveSocket()->getAddress()
: matchingNode->getPublicSocket().getAddress(); : matchingNode->getPublicSocket().getAddress();
@ -673,32 +696,41 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid()); NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid());
// check if there were already permissions for the IP // check if there were already permissions for the IP
hadExistingPermissions = hasPermissionsForIP(kickAddress); bool hadIPPermissions = hasPermissionsForIP(kickAddress);
// grab or create permissions for the given IP address // grab or create permissions for the given IP address
destinationPermissions = _ipPermissions[ipAddressKey]; auto ipPermissions = _ipPermissions[ipAddressKey];
if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
newPermissions = true;
ipPermissions->clear(NodePermissions::Permission::canConnectToDomain);
}
// potentially remove connect permissions for the MAC address
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
if (nodeData) {
NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0);
bool hadMACPermissions = hasPermissionsForMAC(nodeData->getHardwareAddress());
auto macPermissions = _macPermissions[macAddressKey];
if (!hadMACPermissions || macPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
newPermissions = true;
macPermissions->clear(NodePermissions::Permission::canConnectToDomain);
}
}
} }
// make sure we didn't already have existing permissions that disallowed connect if (newPermissions) {
if (!hadExistingPermissions
|| destinationPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
<< "after kick request"; << "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID());
// ensure that the connect permission is clear
destinationPermissions->clear(NodePermissions::Permission::canConnectToDomain);
// we've changed permissions, time to store them to disk and emit our signal to say they have changed // we've changed permissions, time to store them to disk and emit our signal to say they have changed
packPermissions(); packPermissions();
emit updateNodePermissions();
} else { } else {
qWarning() << "Received kick request for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
<< "that already did not have permission to connect";
// in this case, though we don't expect the node to be connected to the domain, it is
// emit updateNodePermissions so that the DomainGatekeeper kicks it out
emit updateNodePermissions(); emit updateNodePermissions();
} }
@ -753,6 +785,16 @@ NodePermissions DomainServerSettingsManager::getPermissionsForIP(const QHostAddr
return nullPermissions; return nullPermissions;
} }
NodePermissions DomainServerSettingsManager::getPermissionsForMAC(const QString& macAddress) const {
NodePermissionsKey macKey = NodePermissionsKey(macAddress, 0);
if (_macPermissions.contains(macKey)) {
return *(_macPermissions[macKey].get());
}
NodePermissions nullPermissions;
nullPermissions.setAll(false);
return nullPermissions;
}
NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const { NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const {
NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID); NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID);
if (_groupPermissions.contains(groupRankKey)) { if (_groupPermissions.contains(groupRankKey)) {

View file

@ -28,6 +28,7 @@ const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
const QString MAC_PERMISSIONS_KEYPATH = "security.mac_permissions";
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
@ -62,6 +63,10 @@ public:
bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); } bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); }
NodePermissions getPermissionsForIP(const QHostAddress& address) const; NodePermissions getPermissionsForIP(const QHostAddress& address) const;
// these give access to permissions for specific MACs from the domain-server settings page
bool hasPermissionsForMAC(const QString& macAddress) const { return _macPermissions.contains(macAddress, 0); }
NodePermissions getPermissionsForMAC(const QString& macAddress) const;
// these give access to permissions for specific groups from the domain-server settings page // these give access to permissions for specific groups from the domain-server settings page
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const { bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
return _groupPermissions.contains(groupName, rankID); return _groupPermissions.contains(groupName, rankID);
@ -142,6 +147,7 @@ private:
NodePermissionsMap _agentPermissions; // specific account-names NodePermissionsMap _agentPermissions; // specific account-names
NodePermissionsMap _ipPermissions; // permissions granted by node IP address NodePermissionsMap _ipPermissions; // permissions granted by node IP address
NodePermissionsMap _macPermissions; // permissions granted by node MAC address
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group

View file

@ -29,6 +29,9 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c
// NOTE: QDataStream::readBytes() - The buffer is allocated using new []. Destroy it with the delete [] operator. // NOTE: QDataStream::readBytes() - The buffer is allocated using new []. Destroy it with the delete [] operator.
delete[] rawBytes; delete[] rawBytes;
// read the hardware address sent by the client
dataStream >> newHeader.hardwareAddress;
} }
dataStream >> newHeader.nodeType dataStream >> newHeader.nodeType

View file

@ -28,6 +28,7 @@ public:
HifiSockAddr senderSockAddr; HifiSockAddr senderSockAddr;
QList<NodeType_t> interestList; QList<NodeType_t> interestList;
QString placeName; QString placeName;
QString hardwareAddress;
QByteArray protocolVersion; QByteArray protocolVersion;
}; };

View file

@ -32,7 +32,7 @@
void setupPreferences() { void setupPreferences() {
auto preferences = DependencyManager::get<Preferences>(); auto preferences = DependencyManager::get<Preferences>();
auto nodeList = DependencyManager::get<NodeList>();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
static const QString AVATAR_BASICS { "Avatar Basics" }; static const QString AVATAR_BASICS { "Avatar Basics" };
{ {
@ -68,6 +68,18 @@ void setupPreferences() {
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); 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 // UI
{ {

View file

@ -39,11 +39,11 @@ bool DebugHmdDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
_uiModelTransform = DependencyManager::get<CompositorHelper>()->getModelTransform(); _uiModelTransform = DependencyManager::get<CompositorHelper>()->getModelTransform();
_frameInfos[frameIndex] = _currentRenderFrameInfo; _frameInfos[frameIndex] = _currentRenderFrameInfo;
_handPoses[0] = glm::translate(mat4(), vec3(-0.3f, 0.0f, 0.0f)); _handPoses[0] = glm::translate(mat4(), vec3(0.3f * cosf(secTimestampNow() * 3.0f), -0.3f * sinf(secTimestampNow() * 5.0f), 0.0f));
_handLasers[0].color = vec4(1, 0, 0, 1); _handLasers[0].color = vec4(1, 0, 0, 1);
_handLasers[0].mode = HandLaserMode::Overlay; _handLasers[0].mode = HandLaserMode::Overlay;
_handPoses[1] = glm::translate(mat4(), vec3(0.3f, 0.0f, 0.0f)); _handPoses[1] = glm::translate(mat4(), vec3(0.3f * sinf(secTimestampNow() * 3.0f), -0.3f * cosf(secTimestampNow() * 5.0f), 0.0f));
_handLasers[1].color = vec4(0, 1, 1, 1); _handLasers[1].color = vec4(0, 1, 1, 1);
_handLasers[1].mode = HandLaserMode::Overlay; _handLasers[1].mode = HandLaserMode::Overlay;
}); });

View file

@ -23,7 +23,6 @@
#include <CursorManager.h> #include <CursorManager.h>
#include <gl/GLWidget.h> #include <gl/GLWidget.h>
#include <shared/NsightHelpers.h> #include <shared/NsightHelpers.h>
#include <GeometryCache.h>
#include <gpu/Context.h> #include <gpu/Context.h>
#include <gpu/StandardShaderLib.h> #include <gpu/StandardShaderLib.h>
#include <gpu/gl/GLBackend.h> #include <gpu/gl/GLBackend.h>
@ -32,6 +31,9 @@
#include "../Logging.h" #include "../Logging.h"
#include "../CompositorHelper.h" #include "../CompositorHelper.h"
#include <../render-utils/shaders/render-utils/glowLine_vert.h>
#include <../render-utils/shaders/render-utils/glowLine_frag.h>
static const QString MONO_PREVIEW = "Mono Preview"; static const QString MONO_PREVIEW = "Mono Preview";
static const QString DISABLE_PREVIEW = "Disable Preview"; static const QString DISABLE_PREVIEW = "Disable Preview";
@ -47,6 +49,12 @@ static const size_t NUMBER_OF_HANDS = 2;
//#define LIVE_SHADER_RELOAD 1 //#define LIVE_SHADER_RELOAD 1
extern glm::vec3 getPoint(float yaw, float pitch); extern glm::vec3 getPoint(float yaw, float pitch);
struct HandLaserData {
vec4 p1;
vec4 p2;
vec4 color;
};
static QString readFile(const QString& filename) { static QString readFile(const QString& filename) {
QFile file(filename); QFile file(filename);
file.open(QFile::Text | QFile::ReadOnly); file.open(QFile::Text | QFile::ReadOnly);
@ -112,11 +120,28 @@ void HmdDisplayPlugin::internalDeactivate() {
void HmdDisplayPlugin::customizeContext() { void HmdDisplayPlugin::customizeContext() {
Parent::customizeContext(); Parent::customizeContext();
_overlayRenderer.build(); _overlayRenderer.build();
auto geometryCache = DependencyManager::get<GeometryCache>();
for (size_t i = 0; i < _geometryIds.size(); ++i) { {
_geometryIds[i] = geometryCache->allocateID(); auto state = std::make_shared<gpu::State>();
} auto VS = gpu::Shader::createVertex(std::string(glowLine_vert));
_extraLaserID = geometryCache->allocateID(); auto PS = gpu::Shader::createPixel(std::string(glowLine_frag));
auto program = gpu::Shader::createProgram(VS, PS);
state->setCullMode(gpu::State::CULL_NONE);
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
gpu::Shader::makeProgram(*program, gpu::Shader::BindingSet());
_glowLinePipeline = gpu::Pipeline::create(program, state);
for (const auto& buffer : program->getBuffers()) {
if (buffer._name == "lineData") {
_handLaserUniformSlot = buffer._location;
}
}
_handLaserUniforms = std::array<gpu::BufferPointer, 2>{ { std::make_shared<gpu::Buffer>(), std::make_shared<gpu::Buffer>() } };
_extraLaserUniforms = std::make_shared<gpu::Buffer>();
};
} }
void HmdDisplayPlugin::uncustomizeContext() { void HmdDisplayPlugin::uncustomizeContext() {
@ -131,12 +156,10 @@ void HmdDisplayPlugin::uncustomizeContext() {
}); });
_overlayRenderer = OverlayRenderer(); _overlayRenderer = OverlayRenderer();
_previewTexture.reset(); _previewTexture.reset();
_handLaserUniforms[0].reset();
auto geometryCache = DependencyManager::get<GeometryCache>(); _handLaserUniforms[1].reset();
for (size_t i = 0; i < _geometryIds.size(); ++i) { _extraLaserUniforms.reset();
geometryCache->releaseID(_geometryIds[i]); _glowLinePipeline.reset();
}
geometryCache->releaseID(_extraLaserID);
Parent::uncustomizeContext(); Parent::uncustomizeContext();
} }
@ -682,12 +705,16 @@ void HmdDisplayPlugin::compositeExtra() {
if (_presentHandPoses[0] == IDENTITY_MATRIX && _presentHandPoses[1] == IDENTITY_MATRIX && !_presentExtraLaser.valid()) { if (_presentHandPoses[0] == IDENTITY_MATRIX && _presentHandPoses[1] == IDENTITY_MATRIX && !_presentExtraLaser.valid()) {
return; return;
} }
auto geometryCache = DependencyManager::get<GeometryCache>();
render([&](gpu::Batch& batch) { render([&](gpu::Batch& batch) {
batch.setFramebuffer(_compositeFramebuffer); batch.setFramebuffer(_compositeFramebuffer);
batch.setModelTransform(Transform());
batch.setViewportTransform(ivec4(uvec2(0), _renderTargetSize)); batch.setViewportTransform(ivec4(uvec2(0), _renderTargetSize));
batch.setViewTransform(_currentPresentFrameInfo.presentPose, false); batch.setViewTransform(_currentPresentFrameInfo.presentPose, false);
// Compile the shaders
batch.setPipeline(_glowLinePipeline);
bilateral::for_each_side([&](bilateral::Side side){ bilateral::for_each_side([&](bilateral::Side side){
auto index = bilateral::index(side); auto index = bilateral::index(side);
if (_presentHandPoses[index] == IDENTITY_MATRIX) { if (_presentHandPoses[index] == IDENTITY_MATRIX) {
@ -696,13 +723,19 @@ void HmdDisplayPlugin::compositeExtra() {
const auto& laser = _presentHandLasers[index]; const auto& laser = _presentHandLasers[index];
if (laser.valid()) { if (laser.valid()) {
const auto& points = _presentHandLaserPoints[index]; const auto& points = _presentHandLaserPoints[index];
geometryCache->renderGlowLine(batch, points.first, points.second, laser.color, _geometryIds[index]); _handLaserUniforms[index]->resize(sizeof(HandLaserData));
_handLaserUniforms[index]->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _handLasers[index].color });
batch.setUniformBuffer(_handLaserUniformSlot, _handLaserUniforms[index]);
batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
} }
}); });
if (_presentExtraLaser.valid()) { if (_presentExtraLaser.valid()) {
const auto& points = _presentExtraLaserPoints; const auto& points = _presentExtraLaserPoints;
geometryCache->renderGlowLine(batch, points.first, points.second, _presentExtraLaser.color, _extraLaserID); _extraLaserUniforms->resize(sizeof(HandLaserData));
_extraLaserUniforms->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _presentExtraLaser.color });
batch.setUniformBuffer(_handLaserUniformSlot, _extraLaserUniforms);
batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
} }
}); });
} }

View file

@ -80,8 +80,6 @@ protected:
Transform _presentUiModelTransform; Transform _presentUiModelTransform;
std::array<HandLaserInfo, 2> _presentHandLasers; std::array<HandLaserInfo, 2> _presentHandLasers;
std::array<int, 2> _geometryIds;
int _extraLaserID;
std::array<mat4, 2> _presentHandPoses; std::array<mat4, 2> _presentHandPoses;
std::array<std::pair<vec3, vec3>, 2> _presentHandLaserPoints; std::array<std::pair<vec3, vec3>, 2> _presentHandLaserPoints;
@ -120,6 +118,10 @@ private:
bool _disablePreviewItemAdded { false }; bool _disablePreviewItemAdded { false };
bool _monoPreview { true }; bool _monoPreview { true };
bool _clearPreviewFlag { false }; bool _clearPreviewFlag { false };
std::array<gpu::BufferPointer, 2> _handLaserUniforms;
uint32_t _handLaserUniformSlot { 0 };
gpu::BufferPointer _extraLaserUniforms;
gpu::PipelinePointer _glowLinePipeline;
gpu::TexturePointer _previewTexture; gpu::TexturePointer _previewTexture;
glm::vec2 _lastWindowSize; glm::vec2 _lastWindowSize;

View file

@ -580,6 +580,11 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
voxelVolumeSize = _voxelVolumeSize; voxelVolumeSize = _voxelVolumeSize;
}); });
if (!mesh ||
!mesh->getIndexBuffer()._buffer) {
return;
}
if (!_pipeline) { if (!_pipeline) {
gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert)); gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert));
gpu::ShaderPointer pixelShader = gpu::Shader::createPixel(std::string(polyvox_frag)); gpu::ShaderPointer pixelShader = gpu::Shader::createPixel(std::string(polyvox_frag));
@ -600,17 +605,23 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
_pipeline = gpu::Pipeline::create(program, state); _pipeline = gpu::Pipeline::create(program, state);
} }
if (!_vertexFormat) {
auto vf = std::make_shared<gpu::Stream::Format>();
vf->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
vf->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 12);
_vertexFormat = vf;
}
gpu::Batch& batch = *args->_batch; gpu::Batch& batch = *args->_batch;
batch.setPipeline(_pipeline); batch.setPipeline(_pipeline);
Transform transform(voxelToWorldMatrix()); Transform transform(voxelToWorldMatrix());
batch.setModelTransform(transform); batch.setModelTransform(transform);
batch.setInputFormat(mesh->getVertexFormat()); batch.setInputFormat(_vertexFormat);
batch.setInputBuffer(gpu::Stream::POSITION, mesh->getVertexBuffer()); batch.setInputBuffer(gpu::Stream::POSITION, mesh->getVertexBuffer()._buffer,
batch.setInputBuffer(gpu::Stream::NORMAL, 0,
mesh->getVertexBuffer()._buffer, sizeof(PolyVox::PositionMaterialNormal));
sizeof(float) * 3,
mesh->getVertexBuffer()._stride);
batch.setIndexBuffer(gpu::UINT32, mesh->getIndexBuffer()._buffer, 0); batch.setIndexBuffer(gpu::UINT32, mesh->getIndexBuffer()._buffer, 0);
if (!_xTextureURL.isEmpty() && !_xTexture) { if (!_xTextureURL.isEmpty() && !_xTexture) {
@ -1097,7 +1108,6 @@ void RenderablePolyVoxEntityItem::getMesh() {
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer()); auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
QtConcurrent::run([entity, voxelSurfaceStyle] { QtConcurrent::run([entity, voxelSurfaceStyle] {
model::MeshPointer mesh(new model::Mesh()); model::MeshPointer mesh(new model::Mesh());
@ -1146,18 +1156,14 @@ void RenderablePolyVoxEntityItem::getMesh() {
auto vertexBuffer = std::make_shared<gpu::Buffer>(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), auto vertexBuffer = std::make_shared<gpu::Buffer>(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal),
(gpu::Byte*)vecVertices.data()); (gpu::Byte*)vecVertices.data());
auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer);
gpu::Resource::Size vertexBufferSize = 0; gpu::BufferView vertexBufferView(vertexBufferPtr, 0,
if (vertexBufferPtr->getSize() > sizeof(float) * 3) { vertexBufferPtr->getSize(),
vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3;
}
gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferSize,
sizeof(PolyVox::PositionMaterialNormal), sizeof(PolyVox::PositionMaterialNormal),
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW));
mesh->setVertexBuffer(vertexBufferView); mesh->setVertexBuffer(vertexBufferView);
mesh->addAttribute(gpu::Stream::NORMAL, mesh->addAttribute(gpu::Stream::NORMAL,
gpu::BufferView(vertexBufferPtr, gpu::BufferView(vertexBufferPtr, sizeof(float) * 3,
sizeof(float) * 3, vertexBufferPtr->getSize() ,
vertexBufferPtr->getSize() - sizeof(float) * 3,
sizeof(PolyVox::PositionMaterialNormal), sizeof(PolyVox::PositionMaterialNormal),
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)));
entity->setMesh(mesh); entity->setMesh(mesh);

View file

@ -149,6 +149,7 @@ private:
// may not match _voxelVolumeSize. // may not match _voxelVolumeSize.
model::MeshPointer _mesh; model::MeshPointer _mesh;
gpu::Stream::FormatPointer _vertexFormat;
bool _meshDirty { true }; // does collision-shape need to be recomputed? bool _meshDirty { true }; // does collision-shape need to be recomputed?
bool _meshInitialized { false }; bool _meshInitialized { false };

View file

@ -64,6 +64,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
{ {
// Update socket's object name // Update socket's object name
setType(_type); setType(_type);
_ignoreRadiusEnabled = false;
} }
void Node::setType(char type) { void Node::setType(char type) {
@ -101,6 +102,15 @@ void Node::addIgnoredNode(const QUuid& otherNodeID) {
} }
} }
void Node::parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message) {
bool enabled;
float radius;
message->readPrimitive(&enabled);
message->readPrimitive(&radius);
_ignoreRadiusEnabled = enabled;
_ignoreRadius = radius;
}
QDataStream& operator<<(QDataStream& out, const Node& node) { QDataStream& operator<<(QDataStream& out, const Node& node) {
out << node._type; out << node._type;
out << node._uuid; out << node._uuid;

View file

@ -74,10 +74,14 @@ public:
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message); void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
void addIgnoredNode(const QUuid& otherNodeID); void addIgnoredNode(const QUuid& otherNodeID);
bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); } bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); }
void parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message);
friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator<<(QDataStream& out, const Node& node);
friend QDataStream& operator>>(QDataStream& in, Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node);
bool isIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled; }
float getIgnoreRadius() { return _ignoreRadiusEnabled ? _ignoreRadius.load() : std::numeric_limits<float>::max(); }
private: private:
// privatize copy and assignment operator to disallow Node copying // privatize copy and assignment operator to disallow Node copying
Node(const Node &otherNode); Node(const Node &otherNode);
@ -94,6 +98,9 @@ private:
MovingPercentile _clockSkewMovingPercentile; MovingPercentile _clockSkewMovingPercentile;
NodePermissions _permissions; NodePermissions _permissions;
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDSet; tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDSet;
std::atomic_bool _ignoreRadiusEnabled;
std::atomic<float> _ignoreRadius { 0.0f };
}; };
Q_DECLARE_METATYPE(Node*) Q_DECLARE_METATYPE(Node*)

View file

@ -18,6 +18,7 @@
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtNetwork/QHostInfo> #include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
#include <LogHandler.h> #include <LogHandler.h>
#include <UUID.h> #include <UUID.h>
@ -346,6 +347,28 @@ void NodeList::sendDomainServerCheckIn() {
// include the protocol version signature in our connect request // include the protocol version signature in our connect request
QByteArray protocolVersionSig = protocolVersionsSignature(); QByteArray protocolVersionSig = protocolVersionsSignature();
packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size()); packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());
// if possible, include the MAC address for the current interface in our connect request
QString hardwareAddress;
for (auto networkInterface : QNetworkInterface::allInterfaces()) {
for (auto interfaceAddress : networkInterface.addressEntries()) {
if (interfaceAddress.ip() == _localSockAddr.getAddress()) {
// this is the interface whose local IP matches what we've detected the current IP to be
hardwareAddress = networkInterface.hardwareAddress();
// stop checking interfaces and addresses
break;
}
}
// stop looping if this was the current interface
if (!hardwareAddress.isEmpty()) {
break;
}
}
packetStream << hardwareAddress;
} }
// pack our data to send to the domain-server including // pack our data to send to the domain-server including
@ -727,9 +750,26 @@ bool NodeList::sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr) {
return _domainHandler.getSockAddr() == sockAddr || LimitedNodeList::sockAddrBelongsToNode(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) { void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
// enumerate the nodes to send a reliable ignore packet to each that can leverage it // enumerate the nodes to send a reliable ignore packet to each that can leverage it
if (!nodeID.isNull() && _sessionUUID != nodeID) { if (!nodeID.isNull() && _sessionUUID != nodeID) {
eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool { eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool {
if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) { if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) {
@ -788,6 +828,9 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
// send this NLPacketList to the new node // send this NLPacketList to the new node
sendPacketList(std::move(ignorePacketList), *newNode); sendPacketList(std::move(ignorePacketList), *newNode);
} }
// also send them the current ignore radius state.
sendIgnoreRadiusStateToNode(newNode);
} }
} }

View file

@ -30,6 +30,7 @@
#include <QtNetwork/QUdpSocket> #include <QtNetwork/QUdpSocket>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <SettingHandle.h>
#include "DomainHandler.h" #include "DomainHandler.h"
#include "LimitedNodeList.h" #include "LimitedNodeList.h"
@ -70,6 +71,12 @@ public:
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } 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); void ignoreNodeBySessionID(const QUuid& nodeID);
bool isIgnoringNode(const QUuid& nodeID) const; bool isIgnoringNode(const QUuid& nodeID) const;
@ -101,7 +108,7 @@ signals:
void limitOfSilentDomainCheckInsReached(); void limitOfSilentDomainCheckInsReached();
void receivedDomainServerList(); void receivedDomainServerList();
void ignoredNode(const QUuid& nodeID); void ignoredNode(const QUuid& nodeID);
private slots: private slots:
void stopKeepalivePingTimer(); void stopKeepalivePingTimer();
void sendPendingDSPathQuery(); void sendPendingDSPathQuery();
@ -146,6 +153,10 @@ private:
mutable QReadWriteLock _ignoredSetLock; mutable QReadWriteLock _ignoredSetLock;
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs; tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false };
Setting::Handle<float> _ignoreRadius { "IgnoreRadius", 1.0f };
#if (PR_BUILD || DEV_BUILD) #if (PR_BUILD || DEV_BUILD)
bool _shouldSendNewerVersion { false }; bool _shouldSendNewerVersion { false };
#endif #endif

View file

@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(DomainConnectionDeniedVersion::IncludesExtraInfo); return static_cast<PacketVersion>(DomainConnectionDeniedVersion::IncludesExtraInfo);
case PacketType::DomainConnectRequest: case PacketType::DomainConnectRequest:
return static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions); return static_cast<PacketVersion>(DomainConnectRequestVersion::HasMACAddress);
case PacketType::DomainServerAddedNode: case PacketType::DomainServerAddedNode:
return static_cast<PacketVersion>(DomainServerAddedNodeVersion::PermissionsGrid); return static_cast<PacketVersion>(DomainServerAddedNodeVersion::PermissionsGrid);

View file

@ -100,7 +100,8 @@ public:
MoreEntityShapes, MoreEntityShapes,
NodeKickRequest, NodeKickRequest,
NodeMuteRequest, NodeMuteRequest,
LAST_PACKET_TYPE = NodeMuteRequest RadiusIgnoreRequest,
LAST_PACKET_TYPE = RadiusIgnoreRequest
}; };
}; };
@ -207,7 +208,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
enum class DomainConnectRequestVersion : PacketVersion { enum class DomainConnectRequestVersion : PacketVersion {
NoHostname = 17, NoHostname = 17,
HasHostname, HasHostname,
HasProtocolVersions HasProtocolVersions,
HasMACAddress
}; };
enum class DomainConnectionDeniedVersion : PacketVersion { enum class DomainConnectionDeniedVersion : PacketVersion {

View file

@ -38,7 +38,6 @@
#include "simple_opaque_web_browser_frag.h" #include "simple_opaque_web_browser_frag.h"
#include "simple_transparent_web_browser_frag.h" #include "simple_transparent_web_browser_frag.h"
#include "glowLine_vert.h" #include "glowLine_vert.h"
#include "glowLine_geom.h"
#include "glowLine_frag.h" #include "glowLine_frag.h"
#include "grid_frag.h" #include "grid_frag.h"
@ -1405,6 +1404,7 @@ GeometryCache::BatchItemDetails::~BatchItemDetails() {
void GeometryCache::BatchItemDetails::clear() { void GeometryCache::BatchItemDetails::clear() {
isCreated = false; isCreated = false;
uniformBuffer.reset();
verticesBuffer.reset(); verticesBuffer.reset();
colorBuffer.reset(); colorBuffer.reset();
streamFormat.reset(); streamFormat.reset();
@ -1593,8 +1593,6 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
glowIntensity = 0.0f; glowIntensity = 0.0f;
#endif #endif
glowIntensity = 0.0f;
if (glowIntensity <= 0) { if (glowIntensity <= 0) {
bindSimpleProgram(batch, false, false, false, true, false); bindSimpleProgram(batch, false, false, false, true, false);
renderLine(batch, p1, p2, color, id); renderLine(batch, p1, p2, color, id);
@ -1602,20 +1600,20 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
} }
// Compile the shaders // Compile the shaders
static const uint32_t LINE_DATA_SLOT = 1;
static std::once_flag once; static std::once_flag once;
std::call_once(once, [&] { std::call_once(once, [&] {
auto state = std::make_shared<gpu::State>(); auto state = std::make_shared<gpu::State>();
auto VS = gpu::Shader::createVertex(std::string(glowLine_vert)); auto VS = gpu::Shader::createVertex(std::string(glowLine_vert));
auto GS = gpu::Shader::createGeometry(std::string(glowLine_geom));
auto PS = gpu::Shader::createPixel(std::string(glowLine_frag)); auto PS = gpu::Shader::createPixel(std::string(glowLine_frag));
auto program = gpu::Shader::createProgram(VS, GS, PS); auto program = gpu::Shader::createProgram(VS, PS);
state->setCullMode(gpu::State::CULL_NONE); state->setCullMode(gpu::State::CULL_NONE);
state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true, state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
gpu::Shader::BindingSet slotBindings; gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING)); slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT));
gpu::Shader::makeProgram(*program, slotBindings); gpu::Shader::makeProgram(*program, slotBindings);
_glowLinePipeline = gpu::Pipeline::create(program, state); _glowLinePipeline = gpu::Pipeline::create(program, state);
}); });
@ -1626,11 +1624,6 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
bool registered = (id != UNKNOWN_ID); bool registered = (id != UNKNOWN_ID);
BatchItemDetails& details = _registeredLine3DVBOs[id]; BatchItemDetails& details = _registeredLine3DVBOs[id];
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
((int(color.y * 255.0f) & 0xFF) << 8) |
((int(color.z * 255.0f) & 0xFF) << 16) |
((int(color.w * 255.0f) & 0xFF) << 24);
// if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed
if (registered && details.isCreated) { if (registered && details.isCreated) {
Vec3Pair& lastKey = _lastRegisteredLine3D[id]; Vec3Pair& lastKey = _lastRegisteredLine3D[id];
@ -1640,47 +1633,25 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
} }
} }
const int FLOATS_PER_VERTEX = 3 + 3; // vertices + normals const int NUM_VERTICES = 4;
const int NUM_POS_COORDS = 3;
const int VERTEX_NORMAL_OFFSET = NUM_POS_COORDS * sizeof(float);
const int vertices = 2;
if (!details.isCreated) { if (!details.isCreated) {
details.isCreated = true; details.isCreated = true;
details.vertices = vertices; details.uniformBuffer = std::make_shared<gpu::Buffer>();
details.vertexSize = FLOATS_PER_VERTEX;
auto verticesBuffer = std::make_shared<gpu::Buffer>(); struct LineData {
auto colorBuffer = std::make_shared<gpu::Buffer>(); vec4 p1;
auto streamFormat = std::make_shared<gpu::Stream::Format>(); vec4 p2;
auto stream = std::make_shared<gpu::BufferStream>(); vec4 color;
};
details.verticesBuffer = verticesBuffer; LineData lineData { vec4(p1, 1.0f), vec4(p2, 1.0f), color };
details.colorBuffer = colorBuffer; details.uniformBuffer->resize(sizeof(LineData));
details.streamFormat = streamFormat; details.uniformBuffer->setSubData(0, lineData);
details.stream = stream;
details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET);
details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
details.stream->addBuffer(details.verticesBuffer, 0, details.streamFormat->getChannels().at(0)._stride);
details.stream->addBuffer(details.colorBuffer, 0, details.streamFormat->getChannels().at(1)._stride);
const glm::vec3 NORMAL(1.0f, 0.0f, 0.0f);
float vertexBuffer[vertices * FLOATS_PER_VERTEX] = {
p1.x, p1.y, p1.z, NORMAL.x, NORMAL.y, NORMAL.z,
p2.x, p2.y, p2.z, NORMAL.x, NORMAL.y, NORMAL.z };
const int NUM_COLOR_SCALARS = 2;
int colors[NUM_COLOR_SCALARS] = { compactColor, compactColor };
details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer);
details.colorBuffer->append(sizeof(colors), (gpu::Byte*) colors);
} }
// this is what it takes to render a quad // The shader requires no vertices, only uniforms.
batch.setInputFormat(details.streamFormat); batch.setUniformBuffer(LINE_DATA_SLOT, details.uniformBuffer);
batch.setInputStream(0, *details.stream); batch.draw(gpu::TRIANGLE_STRIP, NUM_VERTICES, 0);
batch.draw(gpu::LINES, 2, 0);
} }
void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {

View file

@ -369,6 +369,7 @@ private:
static int population; static int population;
gpu::BufferPointer verticesBuffer; gpu::BufferPointer verticesBuffer;
gpu::BufferPointer colorBuffer; gpu::BufferPointer colorBuffer;
gpu::BufferPointer uniformBuffer;
gpu::Stream::FormatPointer streamFormat; gpu::Stream::FormatPointer streamFormat;
gpu::BufferStreamPointer stream; gpu::BufferStreamPointer stream;

View file

@ -10,26 +10,24 @@
// //
layout(location = 0) in vec4 inColor; layout(location = 0) in vec4 inColor;
layout(location = 1) in vec3 inLineDistance;
out vec4 _fragColor; out vec4 _fragColor;
void main(void) { void main(void) {
vec2 d = inLineDistance.xy; // The incoming value actually ranges from -1 to 1, so modify it
d.y = abs(d.y); // so that it goes from 0 -> 1 -> 0 with the solid alpha being at
d.x = abs(d.x); // the center of the line
if (d.x > 1.0) { float alpha = 1.0 - abs(inColor.a);
d.x = (d.x - 1.0) / 0.02;
} else { // Convert from a linear alpha curve to a sharp peaked one
d.x = 0.0; alpha = pow(alpha, 10);
}
float alpha = 1.0 - length(d); // Drop everything where the curve falls off to nearly nothing
if (alpha <= 0.0) { if (alpha <= 0.05) {
discard;
}
alpha = pow(alpha, 10.0);
if (alpha < 0.05) {
discard; discard;
} }
// Emit the color
_fragColor = vec4(inColor.rgb, alpha); _fragColor = vec4(inColor.rgb, alpha);
return;
} }

View file

@ -1,102 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// Created by Bradley Austin Davis on 2016/07/05
// Copyright 2013-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
//
#extension GL_EXT_geometry_shader4 : enable
<@include gpu/Transform.slh@>
<$declareStandardCameraTransform()$>
layout(location = 0) in vec4 inColor[];
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec3 outLineDistance;
layout(lines) in;
layout(triangle_strip, max_vertices = 24) out;
vec3 ndcToEyeSpace(in vec4 v) {
TransformCamera cam = getTransformCamera();
vec4 u = cam._projectionInverse * v;
return u.xyz / u.w;
}
vec2 toScreenSpace(in vec4 v)
{
TransformCamera cam = getTransformCamera();
vec4 u = cam._projection * cam._view * v;
return u.xy / u.w;
}
vec3[2] getOrthogonals(in vec3 n, float scale) {
float yDot = abs(dot(n, vec3(0, 1, 0)));
vec3 result[2];
if (yDot < 0.9) {
result[0] = normalize(cross(n, vec3(0, 1, 0)));
} else {
result[0] = normalize(cross(n, vec3(1, 0, 0)));
}
// The cross of result[0] and n is orthogonal to both, which are orthogonal to each other
result[1] = cross(result[0], n);
result[0] *= scale;
result[1] *= scale;
return result;
}
vec2 orthogonal(vec2 v) {
vec2 result = v.yx;
result.y *= -1.0;
return result;
}
void main() {
vec2 endpoints[2];
vec3 eyeSpace[2];
TransformCamera cam = getTransformCamera();
for (int i = 0; i < 2; ++i) {
eyeSpace[i] = ndcToEyeSpace(gl_PositionIn[i]);
endpoints[i] = gl_PositionIn[i].xy / gl_PositionIn[i].w;
}
vec2 lineNormal = normalize(endpoints[1] - endpoints[0]);
vec2 lineOrthogonal = orthogonal(lineNormal);
lineNormal *= 0.02;
lineOrthogonal *= 0.02;
gl_Position = gl_PositionIn[0];
gl_Position.xy -= lineOrthogonal;
outColor = inColor[0];
outLineDistance = vec3(-1.02, -1, gl_Position.z);
EmitVertex();
gl_Position = gl_PositionIn[0];
gl_Position.xy += lineOrthogonal;
outColor = inColor[0];
outLineDistance = vec3(-1.02, 1, gl_Position.z);
EmitVertex();
gl_Position = gl_PositionIn[1];
gl_Position.xy -= lineOrthogonal;
outColor = inColor[1];
outLineDistance = vec3(1.02, -1, gl_Position.z);
EmitVertex();
gl_Position = gl_PositionIn[1];
gl_Position.xy += lineOrthogonal;
outColor = inColor[1];
outLineDistance = vec3(1.02, 1, gl_Position.z);
EmitVertex();
EndPrimitive();
}

View file

@ -9,18 +9,50 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
<@include gpu/Inputs.slh@>
<@include gpu/Color.slh@>
<@include gpu/Transform.slh@> <@include gpu/Transform.slh@>
<$declareStandardTransform()$> <$declareStandardTransform()$>
layout(std140) uniform lineData {
vec4 p1;
vec4 p2;
vec4 color;
};
layout(location = 0) out vec4 _color; layout(location = 0) out vec4 _color;
void main(void) { void main(void) {
_color = inColor; _color = color;
// standard transform
TransformCamera cam = getTransformCamera(); TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject(); TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
vec4 p1eye, p2eye;
<$transformModelToEyePos(cam, obj, p1, p1eye)$>
<$transformModelToEyePos(cam, obj, p2, p2eye)$>
p1eye /= p1eye.w;
p2eye /= p2eye.w;
// Find the line direction
vec3 v1 = normalize(p1eye.xyz - p2eye.xyz);
// Find the vector from the eye to one of the points
vec3 v2 = normalize(p1eye.xyz);
// The orthogonal vector is the cross product of these two
vec3 orthogonal = cross(v1, v2) * 0.02;
// Deteremine which end to emit based on the vertex id (even / odd)
vec4 eye = (0 == gl_VertexID % 2) ? p1eye : p2eye;
// Add or subtract the orthogonal vector based on a different vertex ID
// calculation
if (gl_VertexID < 2) {
// Use the alpha channel to store the distance from the center in 'quad space'
_color.a = -1.0;
eye.xyz -= orthogonal;
} else {
_color.a = 1.0;
eye.xyz += orthogonal;
}
// Finally, put the eyespace vertex into clip space
<$transformEyeToClipPos(cam, eye, gl_Position)$>
} }

View file

@ -38,3 +38,27 @@ bool UsersScriptingInterface::getCanKick() {
// ask the NodeList to return our ability to kick // ask the NodeList to return our ability to kick
return DependencyManager::get<NodeList>()->getThisNodeCanKick(); return DependencyManager::get<NodeList>()->getThisNodeCanKick();
} }
void UsersScriptingInterface::toggleIgnoreRadius() {
DependencyManager::get<NodeList>()->toggleIgnoreRadius();
}
void UsersScriptingInterface::enableIgnoreRadius() {
DependencyManager::get<NodeList>()->enableIgnoreRadius();
}
void UsersScriptingInterface::disableIgnoreRadius() {
DependencyManager::get<NodeList>()->disableIgnoreRadius();
}
void UsersScriptingInterface::setIgnoreRadius(float radius, bool enabled) {
DependencyManager::get<NodeList>()->ignoreNodesInRadius(radius, enabled);
}
float UsersScriptingInterface::getIgnoreRadius() {
return DependencyManager::get<NodeList>()->getIgnoreRadius();
}
bool UsersScriptingInterface::getIgnoreRadiusEnabled() {
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
}

View file

@ -16,6 +16,9 @@
#include <DependencyManager.h> #include <DependencyManager.h>
/**jsdoc
* @namespace Users
*/
class UsersScriptingInterface : public QObject, public Dependency { class UsersScriptingInterface : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
@ -26,12 +29,75 @@ public:
UsersScriptingInterface(); UsersScriptingInterface();
public slots: 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); 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); 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); 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(); 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: signals:
void canKickChanged(bool canKick); void canKickChanged(bool canKick);
}; };

View file

@ -33,7 +33,8 @@ var DEFAULT_SCRIPTS = [
"system/dialTone.js", "system/dialTone.js",
"system/firstPersonHMD.js", "system/firstPersonHMD.js",
"system/snapshot.js", "system/snapshot.js",
"system/help.js" "system/help.js",
"system/bubble.js"
]; ];
// add a menu item for debugging // add a menu item for debugging

View file

@ -0,0 +1,275 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#FFFFFF;}
.st2{fill:#1E1E1E;}
.st3{fill:#333333;}
</style>
<g id="Layer_2">
<g>
<g>
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
</g>
</g>
<g>
<g>
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
</g>
</g>
<g>
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
</g>
</g>
<g id="Layer_3">
</g>
<g>
<path class="st3" d="M24.3,19.6c-0.5,0.5-0.9,1-1.3,1.5c-0.1,0.1-0.1,0.3-0.1,0.4c0,1,0,2,0.1,3c0,0.4-0.2,0.7-0.7,0.8
c-0.4,0-0.8-0.2-0.8-0.7c-0.1-1.3-0.1-2.5-0.2-3.8c0-0.2,0.1-0.4,0.2-0.6c0.7-0.9,1.4-1.7,2-2.6c0.3-0.4,1-0.9,1.5-0.9
c0.2,0,0.6,0,0.8,0c0.5,0,0.8,0.1,1.1,0.6c0.5,0.7,0.9,1.4,1.4,2.1c0.4,0.7,0.8,1,1.7,1.2c0.6,0.1,1.3,0.3,1.9,0.5
c0.2,0,0.3,0.1,0.5,0.2c0.3,0.2,0.4,0.5,0.3,0.8c-0.1,0.3-0.3,0.4-0.6,0.4c-0.2,0-0.5,0-0.7-0.1c-0.8-0.2-1.6-0.4-2.4-0.6
c-0.5-0.1-0.8-0.4-1.2-0.7c-0.2-0.2-0.3-0.3-0.5-0.5c0,0.2,0,0.3,0,0.4c0,0.7,0,1.4-0.1,2.1c0,0.2-0.2,6.4-0.3,8.9
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.4-0.4,0.6-0.9,0.6c-0.4,0-0.8-0.3-0.9-0.7c-0.1-0.3-0.1-0.6-0.1-1c0-2.7-0.4-6.5-0.5-7
c0-0.5-0.1-0.6-0.1-1.2c-0.1-0.3-0.1-0.5-0.1-0.7C24.2,21.8,24.3,20.8,24.3,19.6z"/>
<path class="st3" d="M27.1,14.7c0,0.7-0.6,1.3-1.4,1.3l-0.3,0c-0.7,0-1.3-0.6-1.3-1.4l0-1.3c0-0.7,0.6-1.3,1.4-1.3l0.3,0
c0.7,0,1.3,0.6,1.3,1.4L27.1,14.7z"/>
</g>
<path class="st3" d="M22,31.5L22,31.5L22,31.5c-0.1,0-0.2,0-0.4-0.1c-2.2-0.7-4.2-2.1-5.7-3.9c-0.5-0.5-0.4-1.4,0.2-1.8
c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.2,1,0.5c0.6,0.7,1.3,1.4,2.1,1.9c0.8,0.5,1.6,0.9,2.5,1.2l0,0c0.3,0.1,0.6,0.3,0.7,0.6
c0.2,0.3,0.2,0.7,0.1,1c-0.1,0.3-0.3,0.5-0.5,0.7C22.5,31.5,22.2,31.6,22,31.5z"/>
<path class="st3" d="M29.5,31.3c-0.5,0-1-0.3-1.2-0.8c-0.1-0.3-0.1-0.7,0-1s0.4-0.6,0.7-0.7c0.8-0.3,1.5-0.7,2.1-1.2
c0.8-0.6,1.6-1.4,2.2-2.2l0,0c0.2-0.3,0.7-0.5,1.1-0.5c0.3,0,0.5,0.1,0.7,0.2c0.3,0.2,0.5,0.5,0.5,0.8c0.1,0.3,0,0.7-0.2,1
c-0.8,1.1-1.7,2-2.8,2.8c-0.8,0.6-1.7,1.1-2.7,1.5C29.9,31.3,29.7,31.3,29.5,31.3z"/>
<path class="st3" d="M14.9,24.7c-0.3,0-0.5-0.1-0.7-0.2c-0.2-0.2-0.4-0.4-0.5-0.6c-0.2-0.7,0.1-1.4,0.8-1.7c0.2-0.1,0.3-0.1,0.5-0.1
c0.3,0,0.5,0.1,0.7,0.2c0.2,0.2,0.4,0.4,0.5,0.6c0.1,0.3,0.1,0.7,0,1c-0.1,0.3-0.4,0.5-0.7,0.7C15.2,24.7,15,24.7,14.9,24.7z"/>
<path class="st3" d="M36.1,24L36.1,24L36.1,24c-0.1,0-0.2,0-0.3-0.1c-0.3-0.1-0.6-0.3-0.8-0.6c-0.2-0.3-0.2-0.6-0.1-1
c0.2-0.6,0.7-0.9,1.3-0.9c0.1,0,0.2,0,0.3,0.1c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1C37.2,23.6,36.7,24,36.1,24z"/>
<path class="st3" d="M14.2,21c-0.3,0-0.7-0.1-0.9-0.4c-0.2-0.2-0.4-0.6-0.4-0.9c0-0.1,0-0.2,0-0.4c0.1-2.2,0.7-4.4,1.9-6.3
c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.4-0.2,0.6-0.1c0.2,0,0.5,0.1,0.6,0.2c0.6,0.4,0.8,1.2,0.4,1.8c-0.9,1.5-1.5,3.2-1.5,5
c0,0.1,0,0.2,0,0.3c0,0.3-0.1,0.7-0.4,0.9C14.9,20.8,14.6,21,14.2,21C14.2,21,14.2,21,14.2,21z"/>
<path class="st3" d="M36.5,20.2c-0.3,0-0.6-0.1-0.9-0.4c-0.2-0.2-0.4-0.5-0.4-0.8c-0.1-0.9-0.2-1.8-0.6-2.7
c-0.3-0.9-0.8-1.7-1.3-2.5l0,0c-0.2-0.3-0.3-0.6-0.2-1c0.1-0.3,0.2-0.6,0.5-0.8c0.2-0.2,0.5-0.3,0.8-0.2c0.4,0,0.8,0.2,1,0.5
c0,0,0,0,0,0l0,0c1.4,1.9,2.2,4.2,2.4,6.5c0,0.3-0.1,0.7-0.3,0.9c-0.2,0.3-0.5,0.4-0.9,0.4C36.6,20.2,36.6,20.2,36.5,20.2z"/>
<path class="st3" d="M18.5,12.1c-0.4,0-0.7-0.2-1-0.5c-0.2-0.3-0.3-0.6-0.3-1c0-0.3,0.2-0.6,0.5-0.9c0.2-0.2,0.5-0.3,0.8-0.3
c0.4,0,0.7,0.2,1,0.5c0.2,0.3,0.3,0.6,0.3,1c0,0.3-0.2,0.6-0.5,0.9C19.1,12,18.8,12.1,18.5,12.1z"/>
<path class="st3" d="M31.6,11.7c-0.2,0-0.5-0.1-0.7-0.2c-0.3-0.2-0.5-0.5-0.5-0.8c-0.1-0.3,0-0.7,0.2-1c0.2-0.4,0.7-0.6,1.1-0.6
c0.2,0,0.5,0.1,0.7,0.2c0.6,0.4,0.7,1.2,0.3,1.8C32.5,11.5,32.1,11.7,31.6,11.7z"/>
<path class="st3" d="M21.9,10.3c-0.5,0-1-0.4-1.2-0.9c-0.1-0.3-0.1-0.7,0.1-1c0.2-0.3,0.4-0.5,0.7-0.6c1.4-0.4,2.8-0.7,4.2-0.6
c0.9,0,1.8,0.1,2.7,0.4c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1c-0.1,0.3-0.2,0.5-0.5,0.7c-0.3,0.2-0.7,0.3-1.1,0.2
c-0.7-0.2-1.4-0.3-2.2-0.3c-1.1,0-2.3,0.1-3.3,0.5C22.2,10.3,22,10.3,21.9,10.3z"/>
<g>
<path class="st3" d="M13.8,40.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1H8.6v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C13.7,40.1,13.8,40.4,13.8,40.8z M9.9,37.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H9.9z M12.5,40.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1H9.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S12.5,40.7,12.5,40.6z"/>
<path class="st3" d="M17.7,41.4c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8
c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5S17.4,41.4,17.7,41.4z"/>
<path class="st3" d="M27.1,40.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C27,40.1,27.1,40.4,27.1,40.8z M23.2,37.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H23.2z M25.8,40.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7H25c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S25.8,40.7,25.8,40.6z"/>
<path class="st3" d="M33.4,40.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C33.3,40.1,33.4,40.4,33.4,40.8z M29.5,37.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H29.5z M32.2,40.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S32.2,40.7,32.2,40.6z"/>
<path class="st3" d="M34.6,42.5v-6.4h1.2v5.3h3.3v1.1H34.6z"/>
<path class="st3" d="M44.6,41.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H44.6z"/>
</g>
<g>
<path class="st1" d="M24.3,69.6c-0.5,0.5-0.9,1-1.3,1.5c-0.1,0.1-0.1,0.3-0.1,0.4c0,1,0,2,0.1,3c0,0.4-0.2,0.7-0.7,0.8
c-0.4,0-0.8-0.2-0.8-0.7c-0.1-1.3-0.1-2.5-0.2-3.8c0-0.2,0.1-0.4,0.2-0.6c0.7-0.9,1.4-1.7,2-2.6c0.3-0.4,1-0.9,1.5-0.9
c0.2,0,0.6,0,0.8,0c0.5,0,0.8,0.1,1.1,0.6c0.5,0.7,0.9,1.4,1.4,2.1c0.4,0.7,0.8,1,1.7,1.2c0.6,0.1,1.3,0.3,1.9,0.5
c0.2,0,0.3,0.1,0.5,0.2c0.3,0.2,0.4,0.5,0.3,0.8c-0.1,0.3-0.3,0.4-0.6,0.4c-0.2,0-0.5,0-0.7-0.1c-0.8-0.2-1.6-0.4-2.4-0.6
c-0.5-0.1-0.8-0.4-1.2-0.7c-0.2-0.2-0.3-0.3-0.5-0.5c0,0.2,0,0.3,0,0.4c0,0.7,0,1.4-0.1,2.1c0,0.2-0.2,6.4-0.3,8.9
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.4-0.4,0.6-0.9,0.6c-0.4,0-0.8-0.3-0.9-0.7c-0.1-0.3-0.1-0.6-0.1-1c0-2.7-0.4-6.5-0.5-7
c0-0.5-0.1-0.6-0.1-1.2c-0.1-0.3-0.1-0.5-0.1-0.7C24.2,71.8,24.3,70.8,24.3,69.6z"/>
<path class="st1" d="M27.1,64.7c0,0.7-0.6,1.3-1.4,1.3l-0.3,0c-0.7,0-1.3-0.6-1.3-1.4l0-1.3c0-0.7,0.6-1.3,1.4-1.3l0.3,0
c0.7,0,1.3,0.6,1.3,1.4L27.1,64.7z"/>
</g>
<path class="st1" d="M22,81.5L22,81.5L22,81.5c-0.1,0-0.2,0-0.4-0.1c-2.2-0.7-4.2-2.1-5.7-3.9c-0.5-0.5-0.4-1.4,0.2-1.8
c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.2,1,0.5c0.6,0.7,1.3,1.4,2.1,1.9c0.8,0.5,1.6,0.9,2.5,1.2l0,0c0.3,0.1,0.6,0.3,0.7,0.6
c0.2,0.3,0.2,0.7,0.1,1c-0.1,0.3-0.3,0.5-0.5,0.7C22.5,81.5,22.2,81.6,22,81.5z"/>
<path class="st1" d="M29.5,81.3c-0.5,0-1-0.3-1.2-0.8c-0.1-0.3-0.1-0.7,0-1s0.4-0.6,0.7-0.7c0.8-0.3,1.5-0.7,2.1-1.2
c0.8-0.6,1.6-1.4,2.2-2.2l0,0c0.2-0.3,0.7-0.5,1.1-0.5c0.3,0,0.5,0.1,0.7,0.2c0.3,0.2,0.5,0.5,0.5,0.8c0.1,0.3,0,0.7-0.2,1
c-0.8,1.1-1.7,2-2.8,2.8c-0.8,0.6-1.7,1.1-2.7,1.5C29.9,81.3,29.7,81.3,29.5,81.3z"/>
<path class="st1" d="M14.9,74.7c-0.3,0-0.5-0.1-0.7-0.2c-0.2-0.2-0.4-0.4-0.5-0.6c-0.2-0.7,0.1-1.4,0.8-1.7c0.2-0.1,0.3-0.1,0.5-0.1
c0.3,0,0.5,0.1,0.7,0.2c0.2,0.2,0.4,0.4,0.5,0.6c0.1,0.3,0.1,0.7,0,1c-0.1,0.3-0.4,0.5-0.7,0.7C15.2,74.7,15,74.7,14.9,74.7z"/>
<path class="st1" d="M36.1,74L36.1,74L36.1,74c-0.1,0-0.2,0-0.3-0.1c-0.3-0.1-0.6-0.3-0.8-0.6c-0.2-0.3-0.2-0.6-0.1-1
c0.2-0.6,0.7-0.9,1.3-0.9c0.1,0,0.2,0,0.3,0.1c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1C37.2,73.6,36.7,74,36.1,74z"/>
<path class="st1" d="M14.2,71c-0.3,0-0.7-0.1-0.9-0.4c-0.2-0.2-0.4-0.6-0.4-0.9c0-0.1,0-0.2,0-0.4c0.1-2.2,0.7-4.4,1.9-6.3
c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.4-0.2,0.6-0.1c0.2,0,0.5,0.1,0.6,0.2c0.6,0.4,0.8,1.2,0.4,1.8c-0.9,1.5-1.5,3.2-1.5,5
c0,0.1,0,0.2,0,0.3c0,0.3-0.1,0.7-0.4,0.9C14.9,70.8,14.6,71,14.2,71C14.2,71,14.2,71,14.2,71z"/>
<path class="st1" d="M36.5,70.2c-0.3,0-0.6-0.1-0.9-0.4c-0.2-0.2-0.4-0.5-0.4-0.8c-0.1-0.9-0.2-1.8-0.6-2.7
c-0.3-0.9-0.8-1.7-1.3-2.5l0,0c-0.2-0.3-0.3-0.6-0.2-1c0.1-0.3,0.2-0.6,0.5-0.8c0.2-0.2,0.5-0.3,0.8-0.2c0.4,0,0.8,0.2,1,0.5
c0,0,0,0,0,0l0,0c1.4,1.9,2.2,4.2,2.4,6.5c0,0.3-0.1,0.7-0.3,0.9c-0.2,0.3-0.5,0.4-0.9,0.4C36.6,70.2,36.6,70.2,36.5,70.2z"/>
<path class="st1" d="M18.5,62.1c-0.4,0-0.7-0.2-1-0.5c-0.2-0.3-0.3-0.6-0.3-1c0-0.3,0.2-0.6,0.5-0.9c0.2-0.2,0.5-0.3,0.8-0.3
c0.4,0,0.7,0.2,1,0.5c0.2,0.3,0.3,0.6,0.3,1c0,0.3-0.2,0.6-0.5,0.9C19.1,62,18.8,62.1,18.5,62.1z"/>
<path class="st1" d="M31.6,61.7c-0.2,0-0.5-0.1-0.7-0.2c-0.3-0.2-0.5-0.5-0.5-0.8s0-0.7,0.2-1c0.2-0.4,0.7-0.6,1.1-0.6
c0.2,0,0.5,0.1,0.7,0.2c0.6,0.4,0.7,1.2,0.3,1.8C32.5,61.5,32.1,61.7,31.6,61.7z"/>
<path class="st1" d="M21.9,60.3c-0.5,0-1-0.4-1.2-0.9c-0.1-0.3-0.1-0.7,0.1-1c0.2-0.3,0.4-0.5,0.7-0.6c1.4-0.4,2.8-0.7,4.2-0.6
c0.9,0,1.8,0.1,2.7,0.4c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1c-0.1,0.3-0.2,0.5-0.5,0.7c-0.3,0.2-0.7,0.3-1.1,0.2
c-0.7-0.2-1.4-0.3-2.2-0.3c-1.1,0-2.3,0.1-3.3,0.5C22.2,60.3,22,60.3,21.9,60.3z"/>
<g>
<path class="st1" d="M13.8,90.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1H8.6v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C13.7,90.1,13.8,90.4,13.8,90.8z M9.9,87.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H9.9z M12.5,90.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1H9.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S12.5,90.7,12.5,90.6z"/>
<path class="st1" d="M17.7,91.4c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8
c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5S17.4,91.4,17.7,91.4z"/>
<path class="st1" d="M27.1,90.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C27,90.1,27.1,90.4,27.1,90.8z M23.2,87.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H23.2z M25.8,90.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7H25c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S25.8,90.7,25.8,90.6z"/>
<path class="st1" d="M33.4,90.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C33.3,90.1,33.4,90.4,33.4,90.8z M29.5,87.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H29.5z M32.2,90.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S32.2,90.7,32.2,90.6z"/>
<path class="st1" d="M34.6,92.5v-6.4h1.2v5.3h3.3v1.1H34.6z"/>
<path class="st1" d="M44.6,91.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H44.6z"/>
</g>
<g>
<path class="st1" d="M24.3,119.6c-0.5,0.5-0.9,1-1.3,1.5c-0.1,0.1-0.1,0.3-0.1,0.4c0,1,0,2,0.1,3c0,0.4-0.2,0.7-0.7,0.8
c-0.4,0-0.8-0.2-0.8-0.7c-0.1-1.3-0.1-2.5-0.2-3.8c0-0.2,0.1-0.4,0.2-0.6c0.7-0.9,1.4-1.7,2-2.6c0.3-0.4,1-0.9,1.5-0.9
c0.2,0,0.6,0,0.8,0c0.5,0,0.8,0.1,1.1,0.6c0.5,0.7,0.9,1.4,1.4,2.1c0.4,0.7,0.8,1,1.7,1.2c0.6,0.1,1.3,0.3,1.9,0.5
c0.2,0,0.3,0.1,0.5,0.2c0.3,0.2,0.4,0.5,0.3,0.8c-0.1,0.3-0.3,0.4-0.6,0.4c-0.2,0-0.5,0-0.7-0.1c-0.8-0.2-1.6-0.4-2.4-0.6
c-0.5-0.1-0.8-0.4-1.2-0.7c-0.2-0.2-0.3-0.3-0.5-0.5c0,0.2,0,0.3,0,0.4c0,0.7,0,1.4-0.1,2.1c0,0.2-0.2,6.4-0.3,8.9
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.4-0.4,0.6-0.9,0.6c-0.4,0-0.8-0.3-0.9-0.7c-0.1-0.3-0.1-0.6-0.1-1c0-2.7-0.4-6.5-0.5-7
c0-0.5-0.1-0.6-0.1-1.2c-0.1-0.3-0.1-0.5-0.1-0.7C24.2,121.8,24.3,120.8,24.3,119.6z"/>
<path class="st1" d="M27.1,114.7c0,0.7-0.6,1.3-1.4,1.3l-0.3,0c-0.7,0-1.3-0.6-1.3-1.4l0-1.3c0-0.7,0.6-1.3,1.4-1.3l0.3,0
c0.7,0,1.3,0.6,1.3,1.4L27.1,114.7z"/>
</g>
<path class="st1" d="M22,131.5L22,131.5L22,131.5c-0.1,0-0.2,0-0.4-0.1c-2.2-0.7-4.2-2.1-5.7-3.9c-0.5-0.5-0.4-1.4,0.2-1.8
c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.2,1,0.5c0.6,0.7,1.3,1.4,2.1,1.9c0.8,0.5,1.6,0.9,2.5,1.2l0,0c0.3,0.1,0.6,0.3,0.7,0.6
c0.2,0.3,0.2,0.7,0.1,1c-0.1,0.3-0.3,0.5-0.5,0.7C22.5,131.5,22.2,131.6,22,131.5z"/>
<path class="st1" d="M29.5,131.3c-0.5,0-1-0.3-1.2-0.8c-0.1-0.3-0.1-0.7,0-1c0.1-0.3,0.4-0.6,0.7-0.7c0.8-0.3,1.5-0.7,2.1-1.2
c0.8-0.6,1.6-1.4,2.2-2.2l0,0c0.2-0.3,0.7-0.5,1.1-0.5c0.3,0,0.5,0.1,0.7,0.2c0.3,0.2,0.5,0.5,0.5,0.8c0.1,0.3,0,0.7-0.2,1
c-0.8,1.1-1.7,2-2.8,2.8c-0.8,0.6-1.7,1.1-2.7,1.5C29.9,131.3,29.7,131.3,29.5,131.3z"/>
<path class="st1" d="M14.9,124.7c-0.3,0-0.5-0.1-0.7-0.2c-0.2-0.2-0.4-0.4-0.5-0.6c-0.2-0.7,0.1-1.4,0.8-1.7
c0.2-0.1,0.3-0.1,0.5-0.1c0.3,0,0.5,0.1,0.7,0.2c0.2,0.2,0.4,0.4,0.5,0.6c0.1,0.3,0.1,0.7,0,1c-0.1,0.3-0.4,0.5-0.7,0.7
C15.2,124.7,15,124.7,14.9,124.7z"/>
<path class="st1" d="M36.1,124L36.1,124L36.1,124c-0.1,0-0.2,0-0.3-0.1c-0.3-0.1-0.6-0.3-0.8-0.6c-0.2-0.3-0.2-0.6-0.1-1
c0.2-0.6,0.7-0.9,1.3-0.9c0.1,0,0.2,0,0.3,0.1c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1C37.2,123.6,36.7,124,36.1,124z"/>
<path class="st1" d="M14.2,121c-0.3,0-0.7-0.1-0.9-0.4c-0.2-0.2-0.4-0.6-0.4-0.9c0-0.1,0-0.2,0-0.4c0.1-2.2,0.7-4.4,1.9-6.3
c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.4-0.2,0.6-0.1c0.2,0,0.5,0.1,0.6,0.2c0.6,0.4,0.8,1.2,0.4,1.8c-0.9,1.5-1.5,3.2-1.5,5
c0,0.1,0,0.2,0,0.3c0,0.3-0.1,0.7-0.4,0.9C14.9,120.8,14.6,121,14.2,121C14.2,121,14.2,121,14.2,121z"/>
<path class="st1" d="M36.5,120.2c-0.3,0-0.6-0.1-0.9-0.4c-0.2-0.2-0.4-0.5-0.4-0.8c-0.1-0.9-0.2-1.8-0.6-2.7
c-0.3-0.9-0.8-1.7-1.3-2.5l0,0c-0.2-0.3-0.3-0.6-0.2-1c0.1-0.3,0.2-0.6,0.5-0.8c0.2-0.2,0.5-0.3,0.8-0.2c0.4,0,0.8,0.2,1,0.5
c0,0,0,0,0,0l0,0c1.4,1.9,2.2,4.2,2.4,6.5c0,0.3-0.1,0.7-0.3,0.9c-0.2,0.3-0.5,0.4-0.9,0.4C36.6,120.2,36.6,120.2,36.5,120.2z"/>
<path class="st1" d="M18.5,112.1c-0.4,0-0.7-0.2-1-0.5c-0.2-0.3-0.3-0.6-0.3-1c0-0.3,0.2-0.6,0.5-0.9c0.2-0.2,0.5-0.3,0.8-0.3
c0.4,0,0.7,0.2,1,0.5c0.2,0.3,0.3,0.6,0.3,1c0,0.3-0.2,0.6-0.5,0.9C19.1,112,18.8,112.1,18.5,112.1z"/>
<path class="st1" d="M31.6,111.7c-0.2,0-0.5-0.1-0.7-0.2c-0.3-0.2-0.5-0.5-0.5-0.8s0-0.7,0.2-1c0.2-0.4,0.7-0.6,1.1-0.6
c0.2,0,0.5,0.1,0.7,0.2c0.6,0.4,0.7,1.2,0.3,1.8C32.5,111.5,32.1,111.7,31.6,111.7z"/>
<path class="st1" d="M21.9,110.3c-0.5,0-1-0.4-1.2-0.9c-0.1-0.3-0.1-0.7,0.1-1c0.2-0.3,0.4-0.5,0.7-0.6c1.4-0.4,2.8-0.7,4.2-0.6
c0.9,0,1.8,0.1,2.7,0.4c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1c-0.1,0.3-0.2,0.5-0.5,0.7c-0.3,0.2-0.7,0.3-1.1,0.2
c-0.7-0.2-1.4-0.3-2.2-0.3c-1.1,0-2.3,0.1-3.3,0.5C22.2,110.3,22,110.3,21.9,110.3z"/>
<g>
<path class="st1" d="M13.8,140.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1H8.6v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C13.7,140.1,13.8,140.4,13.8,140.8z M9.9,137.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H9.9z M12.5,140.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1H9.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S12.5,140.7,12.5,140.6z"/>
<path class="st1" d="M17.7,141.4c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8
c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5S17.4,141.4,17.7,141.4z"/>
<path class="st1" d="M27.1,140.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C27,140.1,27.1,140.4,27.1,140.8z M23.2,137.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H23.2z M25.8,140.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7H25c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S25.8,140.7,25.8,140.6z"/>
<path class="st1" d="M33.4,140.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C33.3,140.1,33.4,140.4,33.4,140.8z M29.5,137.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H29.5z M32.2,140.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S32.2,140.7,32.2,140.6z"/>
<path class="st1" d="M34.6,142.5v-6.4h1.2v5.3h3.3v1.1H34.6z"/>
<path class="st1" d="M44.6,141.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H44.6z"/>
</g>
<g>
<path class="st1" d="M24.3,169.6c-0.5,0.5-0.9,1-1.3,1.5c-0.1,0.1-0.1,0.3-0.1,0.4c0,1,0,2,0.1,3c0,0.4-0.2,0.7-0.7,0.8
c-0.4,0-0.8-0.2-0.8-0.7c-0.1-1.3-0.1-2.5-0.2-3.8c0-0.2,0.1-0.4,0.2-0.6c0.7-0.9,1.4-1.7,2-2.6c0.3-0.4,1-0.9,1.5-0.9
c0.2,0,0.6,0,0.8,0c0.5,0,0.8,0.1,1.1,0.6c0.5,0.7,0.9,1.4,1.4,2.1c0.4,0.7,0.8,1,1.7,1.2c0.6,0.1,1.3,0.3,1.9,0.5
c0.2,0,0.3,0.1,0.5,0.2c0.3,0.2,0.4,0.5,0.3,0.8c-0.1,0.3-0.3,0.4-0.6,0.4c-0.2,0-0.5,0-0.7-0.1c-0.8-0.2-1.6-0.4-2.4-0.6
c-0.5-0.1-0.8-0.4-1.2-0.7c-0.2-0.2-0.3-0.3-0.5-0.5c0,0.2,0,0.3,0,0.4c0,0.7,0,1.4-0.1,2.1c0,0.2-0.2,6.4-0.3,8.9
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.4-0.4,0.6-0.9,0.6c-0.4,0-0.8-0.3-0.9-0.7c-0.1-0.3-0.1-0.6-0.1-1c0-2.7-0.4-6.5-0.5-7
c0-0.5-0.1-0.6-0.1-1.2c-0.1-0.3-0.1-0.5-0.1-0.7C24.2,171.8,24.3,170.8,24.3,169.6z"/>
<path class="st1" d="M27.1,164.7c0,0.7-0.6,1.3-1.4,1.3l-0.3,0c-0.7,0-1.3-0.6-1.3-1.4l0-1.3c0-0.7,0.6-1.3,1.4-1.3l0.3,0
c0.7,0,1.3,0.6,1.3,1.4L27.1,164.7z"/>
</g>
<path class="st1" d="M22,181.5L22,181.5L22,181.5c-0.1,0-0.2,0-0.4-0.1c-2.2-0.7-4.2-2.1-5.7-3.9c-0.5-0.5-0.4-1.4,0.2-1.8
c0.2-0.2,0.5-0.3,0.9-0.3c0.4,0,0.7,0.2,1,0.5c0.6,0.7,1.3,1.4,2.1,1.9c0.8,0.5,1.6,0.9,2.5,1.2l0,0c0.3,0.1,0.6,0.3,0.7,0.6
c0.2,0.3,0.2,0.7,0.1,1c-0.1,0.3-0.3,0.5-0.5,0.7C22.5,181.5,22.2,181.6,22,181.5z"/>
<path class="st1" d="M29.5,181.3c-0.5,0-1-0.3-1.2-0.8c-0.1-0.3-0.1-0.7,0-1c0.1-0.3,0.4-0.6,0.7-0.7c0.8-0.3,1.5-0.7,2.1-1.2
c0.8-0.6,1.6-1.4,2.2-2.2l0,0c0.2-0.3,0.7-0.5,1.1-0.5c0.3,0,0.5,0.1,0.7,0.2c0.3,0.2,0.5,0.5,0.5,0.8c0.1,0.3,0,0.7-0.2,1
c-0.8,1.1-1.7,2-2.8,2.8c-0.8,0.6-1.7,1.1-2.7,1.5C29.9,181.3,29.7,181.3,29.5,181.3z"/>
<path class="st1" d="M14.9,174.7c-0.3,0-0.5-0.1-0.7-0.2c-0.2-0.2-0.4-0.4-0.5-0.6c-0.2-0.7,0.1-1.4,0.8-1.7
c0.2-0.1,0.3-0.1,0.5-0.1c0.3,0,0.5,0.1,0.7,0.2c0.2,0.2,0.4,0.4,0.5,0.6c0.1,0.3,0.1,0.7,0,1c-0.1,0.3-0.4,0.5-0.7,0.7
C15.2,174.7,15,174.7,14.9,174.7z"/>
<path class="st1" d="M36.1,174L36.1,174L36.1,174c-0.1,0-0.2,0-0.3-0.1c-0.3-0.1-0.6-0.3-0.8-0.6c-0.2-0.3-0.2-0.6-0.1-1
c0.2-0.6,0.7-0.9,1.3-0.9c0.1,0,0.2,0,0.3,0.1c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1C37.2,173.6,36.7,174,36.1,174z"/>
<path class="st1" d="M14.2,171c-0.3,0-0.7-0.1-0.9-0.4c-0.2-0.2-0.4-0.6-0.4-0.9c0-0.1,0-0.2,0-0.4c0.1-2.2,0.7-4.4,1.9-6.3
c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.4-0.2,0.6-0.1c0.2,0,0.5,0.1,0.6,0.2c0.6,0.4,0.8,1.2,0.4,1.8c-0.9,1.5-1.5,3.2-1.5,5
c0,0.1,0,0.2,0,0.3c0,0.3-0.1,0.7-0.4,0.9C14.9,170.8,14.6,171,14.2,171C14.2,171,14.2,171,14.2,171z"/>
<path class="st1" d="M36.5,170.2c-0.3,0-0.6-0.1-0.9-0.4c-0.2-0.2-0.4-0.5-0.4-0.8c-0.1-0.9-0.2-1.8-0.6-2.7
c-0.3-0.9-0.8-1.7-1.3-2.5l0,0c-0.2-0.3-0.3-0.6-0.2-1c0.1-0.3,0.2-0.6,0.5-0.8c0.2-0.2,0.5-0.3,0.8-0.2c0.4,0,0.8,0.2,1,0.5
c0,0,0,0,0,0l0,0c1.4,1.9,2.2,4.2,2.4,6.5c0,0.3-0.1,0.7-0.3,0.9c-0.2,0.3-0.5,0.4-0.9,0.4C36.6,170.2,36.6,170.2,36.5,170.2z"/>
<path class="st1" d="M18.5,162.1c-0.4,0-0.7-0.2-1-0.5c-0.2-0.3-0.3-0.6-0.3-1c0-0.3,0.2-0.6,0.5-0.9c0.2-0.2,0.5-0.3,0.8-0.3
c0.4,0,0.7,0.2,1,0.5c0.2,0.3,0.3,0.6,0.3,1c0,0.3-0.2,0.6-0.5,0.9C19.1,162,18.8,162.1,18.5,162.1z"/>
<path class="st1" d="M31.6,161.7c-0.2,0-0.5-0.1-0.7-0.2c-0.3-0.2-0.5-0.5-0.5-0.8s0-0.7,0.2-1c0.2-0.4,0.7-0.6,1.1-0.6
c0.2,0,0.5,0.1,0.7,0.2c0.6,0.4,0.7,1.2,0.3,1.8C32.5,161.5,32.1,161.7,31.6,161.7z"/>
<path class="st1" d="M21.9,160.3c-0.5,0-1-0.4-1.2-0.9c-0.1-0.3-0.1-0.7,0.1-1c0.2-0.3,0.4-0.5,0.7-0.6c1.4-0.4,2.8-0.7,4.2-0.6
c0.9,0,1.8,0.1,2.7,0.4c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.2,0.6,0.1,1c-0.1,0.3-0.2,0.5-0.5,0.7c-0.3,0.2-0.7,0.3-1.1,0.2
c-0.7-0.2-1.4-0.3-2.2-0.3c-1.1,0-2.3,0.1-3.3,0.5C22.2,160.3,22,160.3,21.9,160.3z"/>
<g>
<path class="st1" d="M13.8,190.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1H8.6v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C13.7,190.1,13.8,190.4,13.8,190.8z M9.9,187.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H9.9z M12.5,190.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1H9.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S12.5,190.7,12.5,190.6z"/>
<path class="st1" d="M17.7,191.4c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8
c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5S17.4,191.4,17.7,191.4z"/>
<path class="st1" d="M27.1,190.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C27,190.1,27.1,190.4,27.1,190.8z M23.2,187.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H23.2z M25.8,190.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7H25c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S25.8,190.7,25.8,190.6z"/>
<path class="st1" d="M33.4,190.8c0,0.3-0.1,0.5-0.2,0.7c-0.1,0.2-0.3,0.4-0.4,0.5c-0.2,0.1-0.4,0.2-0.7,0.3
c-0.2,0.1-0.5,0.1-0.8,0.1h-3.1v-6.4h3.4c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.4s0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6
c0,0.3-0.1,0.6-0.2,0.9c-0.2,0.3-0.4,0.5-0.7,0.6c0.4,0.1,0.7,0.3,0.9,0.6C33.3,190.1,33.4,190.4,33.4,190.8z M29.5,187.1v1.6h1.7
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6c-0.1-0.1-0.3-0.2-0.5-0.2H29.5z M32.2,190.6
c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.1-0.1-0.2-0.2s-0.2-0.1-0.3-0.1h-1.9v1.7h1.8c0.1,0,0.2,0,0.3-0.1
c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3S32.2,190.7,32.2,190.6z"/>
<path class="st1" d="M34.6,192.5v-6.4h1.2v5.3h3.3v1.1H34.6z"/>
<path class="st1" d="M44.6,191.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H44.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

58
scripts/system/bubble.js Normal file
View file

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