Merge branch 'tablet-ui-edit-js' of github.com:sethalves/hifi into tablet-ui-edit-js

This commit is contained in:
Seth Alves 2017-02-23 17:41:30 -08:00
commit c725393423
37 changed files with 2337 additions and 895 deletions

View file

@ -434,8 +434,16 @@ void Agent::executeScript() {
connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater);
_avatarAudioTimerThread.start(); _avatarAudioTimerThread.start();
// 60Hz timer for avatar // Agents should run at 45hz
QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatar); static const int AVATAR_DATA_HZ = 45;
static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ;
QTimer* avatarDataTimer = new QTimer(this);
connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar);
avatarDataTimer->setSingleShot(false);
avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS);
avatarDataTimer->setTimerType(Qt::PreciseTimer);
avatarDataTimer->start();
_scriptEngine->run(); _scriptEngine->run();
Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
@ -538,7 +546,7 @@ void Agent::processAgentAvatar() {
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData; AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData;
QByteArray avatarByteArray = scriptedAvatar->toByteArray(dataDetail, 0, scriptedAvatar->getLastSentJointData()); QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail);
scriptedAvatar->doneEncoding(true); scriptedAvatar->doneEncoding(true);
static AvatarDataSequenceNumber sequenceNumber = 0; static AvatarDataSequenceNumber sequenceNumber = 0;

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,8 @@
#include <ThreadedAssignment.h> #include <ThreadedAssignment.h>
#include "AvatarMixerClientData.h" #include "AvatarMixerClientData.h"
#include "AvatarMixerSlavePool.h"
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients /// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
class AvatarMixer : public ThreadedAssignment { class AvatarMixer : public ThreadedAssignment {
Q_OBJECT Q_OBJECT
@ -36,8 +38,8 @@ public slots:
void sendStatsPacket() override; void sendStatsPacket() override;
private slots: private slots:
void queueIncomingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
void handleViewFrustumPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleViewFrustumPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
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);
@ -45,22 +47,29 @@ private slots:
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void domainSettingsRequestComplete(); void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
void start();
private: private:
void broadcastAvatarData(); AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node);
std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp);
void throttle(std::chrono::microseconds duration, int frame);
void parseDomainServerSettings(const QJsonObject& domainSettings); void parseDomainServerSettings(const QJsonObject& domainSettings);
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
QThread _broadcastThread; void manageDisplayName(const SharedNodePointer& node);
p_high_resolution_clock::time_point _lastFrameTimestamp; p_high_resolution_clock::time_point _lastFrameTimestamp;
float _trailingSleepRatio { 1.0f }; // FIXME - new throttling - use these values somehow
float _performanceThrottlingRatio { 0.0f }; float _trailingMixRatio { 0.0f };
float _throttlingRatio { 0.0f };
int _sumListeners { 0 }; int _sumListeners { 0 };
int _numStatFrames { 0 }; int _numStatFrames { 0 };
int _numTightLoopFrames { 0 };
int _sumIdentityPackets { 0 }; int _sumIdentityPackets { 0 };
float _maxKbpsPerNode = 0.0f; float _maxKbpsPerNode = 0.0f;
@ -68,11 +77,40 @@ private:
float _domainMinimumScale { MIN_AVATAR_SCALE }; float _domainMinimumScale { MIN_AVATAR_SCALE };
float _domainMaximumScale { MAX_AVATAR_SCALE }; float _domainMaximumScale { MAX_AVATAR_SCALE };
QTimer* _broadcastTimer = nullptr;
RateCounter<> _broadcastRate; RateCounter<> _broadcastRate;
p_high_resolution_clock::time_point _lastDebugMessage; p_high_resolution_clock::time_point _lastDebugMessage;
QHash<QString, QPair<int, int>> _sessionDisplayNames; QHash<QString, QPair<int, int>> _sessionDisplayNames;
quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window
quint64 _ignoreCalculationElapsedTime { 0 };
quint64 _avatarDataPackingElapsedTime { 0 };
quint64 _packetSendingElapsedTime { 0 };
quint64 _broadcastAvatarDataElapsedTime { 0 }; // total time spent in broadcastAvatarData since last stats window
quint64 _broadcastAvatarDataInner { 0 };
quint64 _broadcastAvatarDataLockWait { 0 };
quint64 _broadcastAvatarDataNodeTransform { 0 };
quint64 _broadcastAvatarDataNodeFunctor { 0 };
quint64 _handleViewFrustumPacketElapsedTime { 0 };
quint64 _handleAvatarIdentityPacketElapsedTime { 0 };
quint64 _handleKillAvatarPacketElapsedTime { 0 };
quint64 _handleNodeIgnoreRequestPacketElapsedTime { 0 };
quint64 _handleRadiusIgnoreRequestPacketElapsedTime { 0 };
quint64 _handleRequestsDomainListDataPacketElapsedTime { 0 };
quint64 _processQueuedAvatarDataPacketsElapsedTime { 0 };
quint64 _processQueuedAvatarDataPacketsLockWaitElapsedTime { 0 };
quint64 _processEventsElapsedTime { 0 };
quint64 _sendStatsElapsedTime { 0 };
quint64 _queueIncomingPacketElapsedTime { 0 };
quint64 _lastStatsTime { usecTimestampNow() };
RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs
AvatarMixerSlavePool _slavePool;
}; };
#endif // hifi_AvatarMixer_h #endif // hifi_AvatarMixer_h

View file

@ -16,10 +16,52 @@
#include "AvatarMixerClientData.h" #include "AvatarMixerClientData.h"
void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
if (!_packetQueue.node) {
_packetQueue.node = node;
}
_packetQueue.push(message);
}
int AvatarMixerClientData::processPackets() {
int packetsProcessed = 0;
SharedNodePointer node = _packetQueue.node;
assert(_packetQueue.empty() || node);
_packetQueue.node.clear();
while (!_packetQueue.empty()) {
auto& packet = _packetQueue.front();
packetsProcessed++;
switch (packet->getType()) {
case PacketType::AvatarData:
parseData(*packet);
break;
default:
Q_UNREACHABLE();
}
_packetQueue.pop();
}
assert(_packetQueue.empty());
return packetsProcessed;
}
int AvatarMixerClientData::parseData(ReceivedMessage& message) { int AvatarMixerClientData::parseData(ReceivedMessage& message) {
// pull the sequence number from the data first // pull the sequence number from the data first
message.readPrimitive(&_lastReceivedSequenceNumber); uint16_t sequenceNumber;
message.readPrimitive(&sequenceNumber);
if (sequenceNumber < _lastReceivedSequenceNumber && _lastReceivedSequenceNumber != UINT16_MAX) {
incrementNumOutOfOrderSends();
}
_lastReceivedSequenceNumber = sequenceNumber;
// compute the offset to the data payload // compute the offset to the data payload
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
} }

View file

@ -16,6 +16,7 @@
#include <cfloat> #include <cfloat>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <queue>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QUrl> #include <QtCore/QUrl>
@ -41,6 +42,7 @@ public:
int parseData(ReceivedMessage& message) override; int parseData(ReceivedMessage& message) override;
AvatarData& getAvatar() { return *_avatar; } AvatarData& getAvatar() { return *_avatar; }
const AvatarData* getConstAvatarData() const { return _avatar.get(); }
bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid);
@ -85,9 +87,9 @@ public:
void loadJSONStats(QJsonObject& jsonObject) const; void loadJSONStats(QJsonObject& jsonObject) const;
glm::vec3 getPosition() { return _avatar ? _avatar->getPosition() : glm::vec3(0); } glm::vec3 getPosition() const { return _avatar ? _avatar->getPosition() : glm::vec3(0); }
glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other);
void ignoreOther(SharedNodePointer self, SharedNodePointer other); void ignoreOther(SharedNodePointer self, SharedNodePointer other);
@ -118,9 +120,15 @@ public:
return _lastOtherAvatarSentJoints[otherAvatar]; return _lastOtherAvatarSentJoints[otherAvatar];
} }
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
int processPackets(); // returns number of packets processed
private: private:
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
QWeakPointer<Node> node;
};
PacketQueue _packetQueue;
AvatarSharedPointer _avatar { new AvatarData() }; AvatarSharedPointer _avatar { new AvatarData() };
uint16_t _lastReceivedSequenceNumber { 0 }; uint16_t _lastReceivedSequenceNumber { 0 };

View file

@ -0,0 +1,443 @@
//
// AvatarMixerSlave.cpp
// assignment-client/src/avatar
//
// Created by Brad Hefta-Gaub on 2/14/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <algorithm>
#include <random>
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <AvatarLogging.h>
#include <LogHandler.h>
#include <NetworkAccessManager.h>
#include <NodeList.h>
#include <Node.h>
#include <OctreeConstants.h>
#include <udt/PacketHeaders.h>
#include <SharedUtil.h>
#include <StDev.h>
#include <UUID.h>
#include "AvatarMixer.h"
#include "AvatarMixerClientData.h"
#include "AvatarMixerSlave.h"
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
_begin = begin;
_end = end;
}
void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio) {
_begin = begin;
_end = end;
_lastFrameTimestamp = lastFrameTimestamp;
_maxKbpsPerNode = maxKbpsPerNode;
_throttlingRatio = throttlingRatio;
}
void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) {
stats = _stats;
_stats.reset();
}
void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) {
auto start = usecTimestampNow();
auto nodeData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
if (nodeData) {
_stats.nodesProcessed++;
_stats.packetsProcessed += nodeData->processPackets();
}
auto end = usecTimestampNow();
_stats.processIncomingPacketsElapsedTime += (end - start);
}
void AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray();
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122());
identityPacket->write(individualData);
DependencyManager::get<NodeList>()->sendPacket(std::move(identityPacket), *destinationNode);
_stats.numIdentityPackets++;
}
static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
// only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame
// Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times
// per second.
// This value should be a power of two for performance purposes, as the mixer performs a modulo operation every frame
// to determine whether the extra data should be sent.
static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16;
// FIXME - There is some old logic (unchanged as of 2/17/17) that randomly decides to send an identity
// packet. That logic had the following comment about the constants it uses...
//
// An 80% chance of sending a identity packet within a 5 second interval.
// assuming 60 htz update rate.
//
// Assuming the calculation of the constant is in fact correct for 80% and 60hz and 5 seconds (an assumption
// that I have not verified) then the constant is definitely wrong now, since we send at 45hz.
const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
quint64 start = usecTimestampNow();
auto nodeList = DependencyManager::get<NodeList>();
// setup for distributed random floating point values
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::uniform_real_distribution<float> distribution;
if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) {
_stats.nodesBroadcastedTo++;
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
nodeData->resetInViewStats();
const AvatarData& avatar = nodeData->getAvatar();
glm::vec3 myPosition = avatar.getClientGlobalPosition();
// reset the internal state for correct random number distribution
distribution.reset();
// reset the max distance for this frame
float maxAvatarDistanceThisFrame = 0.0f;
// reset the number of sent avatars
nodeData->resetNumAvatarsSentLastFrame();
// keep a counter of the number of considered avatars
int numOtherAvatars = 0;
// keep track of outbound data rate specifically for avatar data
int numAvatarDataBytes = 0;
// keep track of the number of other avatars held back in this frame
int numAvatarsHeldBack = 0;
// keep track of the number of other avatar frames skipped
int numAvatarsWithSkippedFrames = 0;
// use the data rate specifically for avatar data for FRD adjustment checks
float avatarDataRateLastSecond = nodeData->getOutboundAvatarDataKbps();
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that are not in the view frustrum
bool getsOutOfView = nodeData->getRequestsDomainListData();
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that they've ignored
bool getsIgnoredByMe = getsOutOfView;
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
bool getsAnyIgnored = getsIgnoredByMe && node->getCanKick();
// Check if it is time to adjust what we send this client based on the observed
// bandwidth to this node. We do this once a second, which is also the window for
// the bandwidth reported by node->getOutboundBandwidth();
if (nodeData->getNumFramesSinceFRDAdjustment() > AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) {
const float FRD_ADJUSTMENT_ACCEPTABLE_RATIO = 0.8f;
const float HYSTERISIS_GAP = (1 - FRD_ADJUSTMENT_ACCEPTABLE_RATIO);
const float HYSTERISIS_MIDDLE_PERCENTAGE = (1 - (HYSTERISIS_GAP * 0.5f));
// get the current full rate distance so we can work with it
float currentFullRateDistance = nodeData->getFullRateDistance();
if (avatarDataRateLastSecond > _maxKbpsPerNode) {
// is the FRD greater than the farthest avatar?
// if so, before we calculate anything, set it to that distance
currentFullRateDistance = std::min(currentFullRateDistance, nodeData->getMaxAvatarDistance());
// we're adjusting the full rate distance to target a bandwidth in the middle
// of the hysterisis gap
currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
nodeData->setFullRateDistance(currentFullRateDistance);
nodeData->resetNumFramesSinceFRDAdjustment();
} else if (currentFullRateDistance < nodeData->getMaxAvatarDistance()
&& avatarDataRateLastSecond < _maxKbpsPerNode * FRD_ADJUSTMENT_ACCEPTABLE_RATIO) {
// we are constrained AND we've recovered to below the acceptable ratio
// lets adjust the full rate distance to target a bandwidth in the middle of the hyterisis gap
currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
nodeData->setFullRateDistance(currentFullRateDistance);
nodeData->resetNumFramesSinceFRDAdjustment();
}
} else {
nodeData->incrementNumFramesSinceFRDAdjustment();
}
// setup a PacketList for the avatarPackets
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
// this is an AGENT we have received head data from
// send back a packet with other active node data to this node
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
bool shouldConsider = false;
quint64 startIgnoreCalculation = usecTimestampNow();
// make sure we have data for this avatar, that it isn't the same node,
// and isn't an avatar that the viewing node has ignored
// or that has ignored the viewing node
if (!otherNode->getLinkedData()
|| otherNode->getUUID() == node->getUUID()
|| (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe)
|| (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
shouldConsider = false;
} else {
const AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
shouldConsider = true; // assume we will consider...
// Check to see if the space bubble is enabled
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
// Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
// Define the scale of the box for the current node
glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f;
// Define the scale of the box for the current other node
glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f;
// Set up the bounding box for the current node
AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale);
// Clamp the size of the bounding box to a minimum scale
if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) {
nodeBox.setScaleStayCentered(minBubbleSize);
}
// Set up the bounding box for the current other node
AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
// Clamp the size of the bounding box to a minimum scale
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
otherNodeBox.setScaleStayCentered(minBubbleSize);
}
// Quadruple the scale of both bounding boxes
nodeBox.embiggen(4.0f);
otherNodeBox.embiggen(4.0f);
// Perform the collision check between the two bounding boxes
if (nodeBox.touches(otherNodeBox)) {
nodeData->ignoreOther(node, otherNode);
shouldConsider = getsAnyIgnored;
}
}
// Not close enough to ignore
if (shouldConsider) {
nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID());
}
quint64 endIgnoreCalculation = usecTimestampNow();
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
}
if (shouldConsider) {
quint64 startAvatarDataPacking = usecTimestampNow();
++numOtherAvatars;
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
// make sure we send out identity packets to and from new arrivals.
bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID());
// FIXME - this clause seems suspicious "... || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp ..."
if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0
&& (forceSend
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|| distribution(generator) < IDENTITY_SEND_PROBABILITY)) {
sendIdentityPacket(otherNodeData, node);
}
const AvatarData* otherAvatar = otherNodeData->getConstAvatarData();
// Decide whether to send this avatar's data based on it's distance from us
// The full rate distance is the distance at which EVERY update will be sent for this avatar
// at twice the full rate distance, there will be a 50% chance of sending this avatar's update
glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition();
float distanceToAvatar = glm::length(myPosition - otherPosition);
// potentially update the max full rate distance for this frame
maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar);
// This code handles the random dropping of avatar data based on the ratio of
// "getFullRateDistance" to actual distance.
//
// NOTE: If the recieving node is in "PAL mode" then it's asked to get things even that
// are out of view, this also appears to disable this random distribution.
if (distanceToAvatar != 0.0f
&& !getsOutOfView
&& distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) {
quint64 endAvatarDataPacking = usecTimestampNow();
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
shouldConsider = false;
}
if (shouldConsider) {
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID());
AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber();
// FIXME - This code does appear to be working. But it seems brittle.
// It supports determining if the frame of data for this "other"
// avatar has already been sent to the reciever. This has been
// verified to work on a desktop display that renders at 60hz and
// therefore sends to mixer at 30hz. Each second you'd expect to
// have 15 (45hz-30hz) duplicate frames. In this case, the stat
// avg_other_av_skips_per_second does report 15.
//
// make sure we haven't already sent this data from this sender to this receiver
// or that somehow we haven't sent
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
++numAvatarsHeldBack;
quint64 endAvatarDataPacking = usecTimestampNow();
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
shouldConsider = false;
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
++numAvatarsWithSkippedFrames;
}
// we're going to send this avatar
if (shouldConsider) {
// determine if avatar is in view, to determine how much data to include...
glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f;
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
// this throttles the extra data to only be sent every Nth message
if (!isInView && !getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) {
quint64 endAvatarDataPacking = usecTimestampNow();
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
shouldConsider = false;
}
if (shouldConsider) {
// start a new segment in the PacketList for this avatar
avatarPacketList->startSegment();
AvatarData::AvatarDataDetail detail;
if (!isInView && !getsOutOfView) {
detail = AvatarData::MinimumData;
nodeData->incrementAvatarOutOfView();
} else {
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO
? AvatarData::SendAllData : AvatarData::CullSmallData;
nodeData->incrementAvatarInView();
}
{
bool includeThisAvatar = true;
auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID());
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
bool distanceAdjust = true;
glm::vec3 viewerPosition = myPosition;
AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray
bool dropFaceTracking = false;
quint64 start = usecTimestampNow();
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
quint64 end = usecTimestampNow();
_stats.toByteArrayElapsedTime += (end - start);
static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID);
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data";
dropFaceTracking = true; // first try dropping the facial data
bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData";
bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
}
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!";
includeThisAvatar = false;
}
}
if (includeThisAvatar) {
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
numAvatarDataBytes += avatarPacketList->write(bytes);
_stats.numOthersIncluded++;
// increment the number of avatars sent to this reciever
nodeData->incrementNumAvatarsSentLastFrame();
// set the last sent sequence number for this sender on the receiver
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
otherNodeData->getLastReceivedSequenceNumber());
}
}
avatarPacketList->endSegment();
quint64 endAvatarDataPacking = usecTimestampNow();
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
}
}
}
}
});
quint64 startPacketSending = usecTimestampNow();
// close the current packet so that we're always sending something
avatarPacketList->closeCurrentPacket(true);
_stats.numPacketsSent += (int)avatarPacketList->getNumPackets();
_stats.numBytesSent += numAvatarDataBytes;
// send the avatar data PacketList
nodeList->sendPacketList(std::move(avatarPacketList), *node);
// record the bytes sent for other avatar data in the AvatarMixerClientData
nodeData->recordSentAvatarData(numAvatarDataBytes);
// record the number of avatars held back this frame
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
if (numOtherAvatars == 0) {
// update the full rate distance to FLOAT_MAX since we didn't have any other avatars to send
nodeData->setMaxAvatarDistance(FLT_MAX);
} else {
nodeData->setMaxAvatarDistance(maxAvatarDistanceThisFrame);
}
quint64 endPacketSending = usecTimestampNow();
_stats.packetSendingElapsedTime += (endPacketSending - startPacketSending);
}
quint64 end = usecTimestampNow();
_stats.jobElapsedTime += (end - start);
}

View file

@ -0,0 +1,101 @@
//
// AvatarMixerSlave.h
// assignment-client/src/avatar
//
// Created by Brad Hefta-Gaub on 2/14/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AvatarMixerSlave_h
#define hifi_AvatarMixerSlave_h
class AvatarMixerClientData;
class AvatarMixerSlaveStats {
public:
int nodesProcessed { 0 };
int packetsProcessed { 0 };
quint64 processIncomingPacketsElapsedTime { 0 };
int nodesBroadcastedTo { 0 };
int numPacketsSent { 0 };
int numBytesSent { 0 };
int numIdentityPackets { 0 };
int numOthersIncluded { 0 };
quint64 ignoreCalculationElapsedTime { 0 };
quint64 avatarDataPackingElapsedTime { 0 };
quint64 packetSendingElapsedTime { 0 };
quint64 toByteArrayElapsedTime { 0 };
quint64 jobElapsedTime { 0 };
void reset() {
// receiving job stats
nodesProcessed = 0;
packetsProcessed = 0;
processIncomingPacketsElapsedTime = 0;
// sending job stats
nodesBroadcastedTo = 0;
numPacketsSent = 0;
numBytesSent = 0;
numIdentityPackets = 0;
numOthersIncluded = 0;
ignoreCalculationElapsedTime = 0;
avatarDataPackingElapsedTime = 0;
packetSendingElapsedTime = 0;
toByteArrayElapsedTime = 0;
jobElapsedTime = 0;
}
AvatarMixerSlaveStats& operator+=(const AvatarMixerSlaveStats& rhs) {
nodesProcessed += rhs.nodesProcessed;
packetsProcessed += rhs.packetsProcessed;
processIncomingPacketsElapsedTime += rhs.processIncomingPacketsElapsedTime;
nodesBroadcastedTo += rhs.nodesBroadcastedTo;
numPacketsSent += rhs.numPacketsSent;
numBytesSent += rhs.numBytesSent;
numIdentityPackets += rhs.numIdentityPackets;
numOthersIncluded += rhs.numOthersIncluded;
ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime;
avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime;
packetSendingElapsedTime += rhs.packetSendingElapsedTime;
toByteArrayElapsedTime += rhs.toByteArrayElapsedTime;
jobElapsedTime += rhs.jobElapsedTime;
return *this;
}
};
class AvatarMixerSlave {
public:
using ConstIter = NodeList::const_iterator;
void configure(ConstIter begin, ConstIter end);
void configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio);
void processIncomingPackets(const SharedNodePointer& node);
void broadcastAvatarData(const SharedNodePointer& node);
void harvestStats(AvatarMixerSlaveStats& stats);
private:
void sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
// frame state
ConstIter _begin;
ConstIter _end;
p_high_resolution_clock::time_point _lastFrameTimestamp;
float _maxKbpsPerNode { 0.0f };
float _throttlingRatio { 0.0f };
AvatarMixerSlaveStats _stats;
};
#endif // hifi_AvatarMixerSlave_h

View file

@ -0,0 +1,208 @@
//
// AvatarMixerSlavePool.cpp
// assignment-client/src/avatar
//
// Created by Brad Hefta-Gaub on 2/14/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <assert.h>
#include <algorithm>
#include "AvatarMixerSlavePool.h"
void AvatarMixerSlaveThread::run() {
while (true) {
wait();
// iterate over all available nodes
SharedNodePointer node;
while (try_pop(node)) {
(this->*_function)(node);
}
bool stopping = _stop;
notify(stopping);
if (stopping) {
return;
}
}
}
void AvatarMixerSlaveThread::wait() {
{
Lock lock(_pool._mutex);
_pool._slaveCondition.wait(lock, [&] {
assert(_pool._numStarted <= _pool._numThreads);
return _pool._numStarted != _pool._numThreads;
});
++_pool._numStarted;
}
if (_pool._configure) {
_pool._configure(*this);
}
_function = _pool._function;
}
void AvatarMixerSlaveThread::notify(bool stopping) {
{
Lock lock(_pool._mutex);
assert(_pool._numFinished < _pool._numThreads);
++_pool._numFinished;
if (stopping) {
++_pool._numStopped;
}
}
_pool._poolCondition.notify_one();
}
bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) {
return _pool._queue.try_pop(node);
}
#ifdef AVATAR_SINGLE_THREADED
static AvatarMixerSlave slave;
#endif
void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) {
_function = &AvatarMixerSlave::processIncomingPackets;
_configure = [&](AvatarMixerSlave& slave) {
slave.configure(begin, end);
};
run(begin, end);
}
void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio) {
_function = &AvatarMixerSlave::broadcastAvatarData;
_configure = [&](AvatarMixerSlave& slave) {
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio);
};
run(begin, end);
}
void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
_begin = begin;
_end = end;
#ifdef AUDIO_SINGLE_THREADED
_configure(slave);
std::for_each(begin, end, [&](const SharedNodePointer& node) {
_function(slave, node);
});
#else
// fill the queue
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
_queue.emplace(node);
});
{
Lock lock(_mutex);
// run
_numStarted = _numFinished = 0;
_slaveCondition.notify_all();
// wait
_poolCondition.wait(lock, [&] {
assert(_numFinished <= _numThreads);
return _numFinished == _numThreads;
});
assert(_numStarted == _numThreads);
}
assert(_queue.empty());
#endif
}
void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) {
#ifdef AVATAR_SINGLE_THREADED
functor(slave);
#else
for (auto& slave : _slaves) {
functor(*slave.get());
}
#endif
}
void AvatarMixerSlavePool::setNumThreads(int numThreads) {
// clamp to allowed size
{
int maxThreads = QThread::idealThreadCount();
if (maxThreads == -1) {
// idealThreadCount returns -1 if cores cannot be detected
static const int MAX_THREADS_IF_UNKNOWN = 4;
maxThreads = MAX_THREADS_IF_UNKNOWN;
}
int clampedThreads = std::min(std::max(1, numThreads), maxThreads);
if (clampedThreads != numThreads) {
qWarning("%s: clamped to %d (was %d)", __FUNCTION__, clampedThreads, numThreads);
numThreads = clampedThreads;
}
}
resize(numThreads);
}
void AvatarMixerSlavePool::resize(int numThreads) {
assert(_numThreads == (int)_slaves.size());
#ifdef AVATAR_SINGLE_THREADED
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
#else
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
Lock lock(_mutex);
if (numThreads > _numThreads) {
// start new slaves
for (int i = 0; i < numThreads - _numThreads; ++i) {
auto slave = new AvatarMixerSlaveThread(*this);
slave->start();
_slaves.emplace_back(slave);
}
} else if (numThreads < _numThreads) {
auto extraBegin = _slaves.begin() + numThreads;
// mark slaves to stop...
auto slave = extraBegin;
while (slave != _slaves.end()) {
(*slave)->_stop = true;
++slave;
}
// ...cycle them until they do stop...
_numStopped = 0;
while (_numStopped != (_numThreads - numThreads)) {
_numStarted = _numFinished = _numStopped;
_slaveCondition.notify_all();
_poolCondition.wait(lock, [&] {
assert(_numFinished <= _numThreads);
return _numFinished == _numThreads;
});
}
// ...wait for threads to finish...
slave = extraBegin;
while (slave != _slaves.end()) {
QThread* thread = reinterpret_cast<QThread*>(slave->get());
static const int MAX_THREAD_WAIT_TIME = 10;
thread->wait(MAX_THREAD_WAIT_TIME);
++slave;
}
// ...and erase them
_slaves.erase(extraBegin, _slaves.end());
}
_numThreads = _numStarted = _numFinished = numThreads;
assert(_numThreads == (int)_slaves.size());
#endif
}

View file

@ -0,0 +1,104 @@
//
// AvatarMixerSlavePool.h
// assignment-client/src/avatar
//
// Created by Brad Hefta-Gaub on 2/14/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AvatarMixerSlavePool_h
#define hifi_AvatarMixerSlavePool_h
#include <condition_variable>
#include <mutex>
#include <vector>
#include <tbb/concurrent_queue.h>
#include <QThread>
#include <NodeList.h>
#include "AvatarMixerSlave.h"
class AvatarMixerSlavePool;
class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave {
Q_OBJECT
using ConstIter = NodeList::const_iterator;
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
public:
AvatarMixerSlaveThread(AvatarMixerSlavePool& pool) : _pool(pool) {}
void run() override final;
private:
friend class AvatarMixerSlavePool;
void wait();
void notify(bool stopping);
bool try_pop(SharedNodePointer& node);
AvatarMixerSlavePool& _pool;
void (AvatarMixerSlave::*_function)(const SharedNodePointer& node) { nullptr };
bool _stop { false };
};
// Slave pool for audio mixers
// AvatarMixerSlavePool is not thread-safe! It should be instantiated and used from a single thread.
class AvatarMixerSlavePool {
using Queue = tbb::concurrent_queue<SharedNodePointer>;
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
using ConditionVariable = std::condition_variable;
public:
using ConstIter = NodeList::const_iterator;
AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
~AvatarMixerSlavePool() { resize(0); }
// Jobs the slave pool can do...
void processIncomingPackets(ConstIter begin, ConstIter end);
void broadcastAvatarData(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode, float throttlingRatio);
// iterate over all slaves
void each(std::function<void(AvatarMixerSlave& slave)> functor);
void setNumThreads(int numThreads);
int numThreads() { return _numThreads; }
private:
void run(ConstIter begin, ConstIter end);
void resize(int numThreads);
std::vector<std::unique_ptr<AvatarMixerSlaveThread>> _slaves;
friend void AvatarMixerSlaveThread::wait();
friend void AvatarMixerSlaveThread::notify(bool stopping);
friend bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node);
// synchronization state
Mutex _mutex;
ConditionVariable _slaveCondition;
ConditionVariable _poolCondition;
void (AvatarMixerSlave::*_function)(const SharedNodePointer& node);
std::function<void(AvatarMixerSlave&)> _configure;
int _numThreads { 0 };
int _numStarted { 0 }; // guarded by _mutex
int _numFinished { 0 }; // guarded by _mutex
int _numStopped { 0 }; // guarded by _mutex
// frame state
Queue _queue;
ConstIter _begin;
ConstIter _end;
};
#endif // hifi_AvatarMixerSlavePool_h

View file

@ -14,10 +14,9 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include "ScriptableAvatar.h" #include "ScriptableAvatar.h"
QByteArray ScriptableAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
bool distanceAdjust, glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut) {
_globalPosition = getPosition(); _globalPosition = getPosition();
return AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut); return AvatarData::toByteArrayStateful(dataDetail);
} }

View file

@ -28,8 +28,7 @@ public:
Q_INVOKABLE AnimationDetails getAnimationDetails(); Q_INVOKABLE AnimationDetails getAnimationDetails();
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector<JointData>* sentJointDataOut = nullptr) override;
private slots: private slots:

View file

@ -1299,6 +1299,22 @@
"placeholder": 5.0, "placeholder": 5.0,
"default": 5.0, "default": 5.0,
"advanced": true "advanced": true
},
{
"name": "auto_threads",
"label": "Automatically determine thread count",
"type": "checkbox",
"help": "Allow system to determine number of threads (recommended)",
"default": false,
"advanced": true
},
{
"name": "num_threads",
"label": "Number of Threads",
"help": "Threads to spin up for avatar mixing (if not automatically set)",
"placeholder": "1",
"default": "1",
"advanced": true
} }
] ]
} }

View file

@ -0,0 +1,26 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:18;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:19;stroke-linecap:round;stroke-miterlimit:10;}
</style>
<g>
<g>
<path d="M220.3,378.4l-81.1-232c-1-2.8-3.6-4.3-6.6-4.3h-9.2c-3,0-5.6,1.5-6.6,4.3L34,378.5c-0.8,2.1-0.4,5.1,0.9,7
c1.3,1.9,3.4,3.5,5.7,3.5h15.9c3,0,5.6-2.6,6.6-5.4L90.2,305h73.7l27,78.6c1,2.8,3.6,5.4,6.6,5.4h16.2c2.3,0,4.4-1.8,5.7-3.6
C220.7,383.5,221.1,380.5,220.3,378.4z M100.3,276l25.6-73.1c0.6-1.7,1.3-3.3,1.9-5.1c0.6,1.8,1.2,3,1.7,4.5l24.9,73.7H100.3z"/>
<path d="M351.6,215.5c-9.7-11.4-24.3-17.2-43.2-17.2c-17.5,0-35.5,4.7-53.2,14.1c-3.1,1.7-4.6,5.4-3.3,8.7l5.2,13.6
c0.7,1.9,2.3,3.4,4.2,4.1c1.9,0.7,4.1,0.5,5.8-0.6c14.1-8.2,27.7-12.3,40.6-12.3c10.9,0,18.8,3.2,23.3,9.6c5.2,7.3,8,18.4,8,33
v4.6l-22.7,0.7c-25,0.6-44.7,6-58.2,16.1c-14.3,10.7-21.6,25.7-21.6,44.7c0,17.1,4.9,30.9,14.5,40.8c9.7,10,23.2,15.1,40.1,15.1
c12,0,22.8-2.7,32.2-8c5.6-3.1,11.1-7.7,16.4-13.6l1.6,13.3c0.4,3.5,3.4,6.8,6.9,6.8h10.2c3.9,0,7.6-4.4,7.6-8.3V265.9
C366,243.2,361.1,226.7,351.6,215.5z M265.1,335.2c0-11.2,3.5-19.1,10.7-24.1c8-5.6,22.1-9,42-10.1l19.1-0.9v9.6
c0,17.2-3.8,29.9-12,39.1c-8.1,9-19,13.4-33.3,13.4c-8.8,0-15.3-2.2-19.8-6.9C267.3,350.8,265.1,344.2,265.1,335.2z"/>
<path d="M451.3,439.9c-10,0-15.3-8.1-15.3-18c-0.1-22.2-0.2-298.2-0.4-320.4c-0.1-10,5.2-18.2,15.2-18.3c0.1,0,0.1,0,0.2,0
c9.9,0,15.2,8,15.3,18c0.2,22.2,0.3,298.3,0.4,320.5C466.7,431.7,461.4,439.9,451.3,439.9C451.4,439.9,451.3,439.9,451.3,439.9z"
/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,17 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:18;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:19;stroke-linecap:round;stroke-miterlimit:10;}
</style>
<g>
<path d="M451.6,90.8L189.4,64.9c-3.2-0.3-6.2,0.5-8.8,2.4l0.1,0L54.9,155.4l0,0c-0.5,0.3-0.5,0.7-0.9,1c-2.8,2.5-4,6.1-4,9.9v262.5
c0,6.8,4.8,12.6,11.6,13.2l262.3,25.9c0.4,0,0.8,0.1,1.2,0.1c2.7,0,5.3-0.9,7.6-2.4l0,0l125.8-88.2l0,0c0.5-0.3,0.3-0.7,0.7-1
c2.8-2.5,3.8-6.1,3.8-9.9V104.1C463,97.2,458.4,91.5,451.6,90.8z M430.1,364.7l-1.8,1.4c-0.2,0.1-0.3,0.3-0.6,0.4c0,0,0.4,0,0.4,0
L339,429.2v-230l98-69.5v222.2C436,358,435,361.1,430.1,364.7z M203.3,93l210.6,20.8l-92.4,64.7L99.6,156.6l86.7-60.8
c0.2,0,0.3,0,0.5,0c0,0,0,0,0.1,0c3.4-2.1,7.4-3.4,11.4-3.4C200,92.4,201.7,92.6,203.3,93z M313,440L76,416.7V181l237,23.3V440z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,21 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:18;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:19;stroke-linecap:round;stroke-miterlimit:10;}
</style>
<g>
<path d="M417.6,96.6c-90.2-90.2-236.9-90.2-327.1,0c-43.7,43.7-67.8,101.8-67.8,163.6c0,61.8,24.1,119.9,67.8,163.6
c45.1,45.1,104.3,67.6,163.6,67.6c59.2,0,118.5-22.5,163.6-67.6c43.7-43.7,67.8-101.8,67.8-163.6
C485.4,198.4,461.3,140.3,417.6,96.6z M112.7,118.8c30-30,67.2-48.4,106.1-55.3c-7.4,11.3-14,26-19.8,44
c-13.2,41.4-20.5,96.3-20.5,154.5c0,2.8,0,7,0.1,9.8c6.4,1.6,12.8,1.7,19.3,2.8c1.9,0.3,3.9,0.6,5.9,0.8c-0.1-4.4-0.1-8.8-0.1-13.3
c0-116.9,30-193.4,52.3-200c0.5-0.5,1-1,1.4-1.6c50.1,0.8,99.9,20.3,138,58.4c36.6,36.6,57.3,85,58.5,136.6
c-4.8,22.4-81.7,53.2-200.2,53.2c-115.3,0-191.3-29.1-199.6-51.4C54.9,204.9,75.6,155.9,112.7,118.8z M395.5,401.6
C356,441.1,304,460.5,252.1,460c-14.1-8.9-30.3-43.3-39.9-96.6l-25.6-1.4c3.2,19.2,7.4,38.5,12.5,54.3c5.1,16,10.9,29.4,17.3,40.1
c-38-7.2-74.3-25.5-103.7-54.9C83.2,372.1,64,334.9,57.1,294.6c11,6.9,25.2,13.2,42.3,18.7c41.4,13.2,96.3,20.5,154.5,20.5
c58.2,0,113-7.3,154.5-20.5c17.4-5.6,31.7-11.9,42.9-19C444.2,334.7,425.1,372,395.5,401.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,38 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:18;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:19;stroke-linecap:round;stroke-miterlimit:10;}
</style>
<g>
<path d="M380.7,139.8c-2.7-6.6-9.2-10.8-16.3-10.8H292v34h29.9L159,326.1V290h-36v79h1.3c0.1,2,0.5,4.1,1.3,5.9
c2.7,6.6,9.1,11.1,16.2,11.1H225v-35h-40.8L346,189.7V215h35v-62.4C382,148.5,382.4,144,380.7,139.8z"/>
<path d="M338.4,437.6c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C348.1,459.3,338.4,449.6,338.4,437.6z M266.9,437.6c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0
c0,12-9.7,21.7-21.7,21.7l0,0C276.6,459.3,266.9,449.6,266.9,437.6z M195.4,437.6c0-12,9.7-21.7,21.7-21.7l0,0
c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0C205.1,459.3,195.4,449.6,195.4,437.6z M123.9,437.6
c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0C133.6,459.3,123.9,449.6,123.9,437.6z"/>
<path d="M74.1,459.3c-5.7,0-11.3-2.3-15.4-6.4c-4-4-6.4-9.6-6.4-15.3c0-5.7,2.3-11.3,6.4-15.3c4-4,9.6-6.4,15.4-6.4
c5.7,0,11.3,2.3,15.3,6.4c4,4,6.4,9.6,6.4,15.3c0,5.7-2.3,11.3-6.4,15.3C85.4,457,79.8,459.3,74.1,459.3z"/>
<path d="M52.4,366.1c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C62.1,387.8,52.4,378,52.4,366.1z M52.4,294.6c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7
l0,0C62.1,316.3,52.4,306.5,52.4,294.6z M52.4,223.1c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0
c0,12-9.7,21.7-21.7,21.7l0,0C62.1,244.8,52.4,235,52.4,223.1z M52.4,151.5c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7
l0,0c0,12-9.7,21.7-21.7,21.7l0,0C62.1,173.2,52.4,163.5,52.4,151.5z"/>
<path d="M338.4,80c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C348.1,101.7,338.4,92,338.4,80z M266.9,80c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C276.6,101.7,266.9,92,266.9,80z M195.4,80c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C205.1,101.7,195.4,92,195.4,80z M123.9,80c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C133.6,101.7,123.9,92,123.9,80z"/>
<path d="M431.6,101.7c-5.7,0-11.3-2.3-15.3-6.4c-4-4-6.4-9.6-6.4-15.3c0-5.7,2.3-11.3,6.4-15.3c4-4,9.6-6.4,15.3-6.4
s11.3,2.3,15.3,6.4c4,4,6.4,9.6,6.4,15.3c0,5.7-2.3,11.3-6.4,15.3C442.9,99.4,437.4,101.7,431.6,101.7z"/>
<path d="M409.9,366.1c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0
C419.7,387.8,409.9,378.1,409.9,366.1z M409.9,294.6c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0
c0,12-9.7,21.7-21.7,21.7l0,0C419.7,316.3,409.9,306.5,409.9,294.6z M409.9,223.1c0-12,9.7-21.7,21.7-21.7l0,0
c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0C419.7,244.7,409.9,235,409.9,223.1z M409.9,151.5
c0-12,9.7-21.7,21.7-21.7l0,0c12,0,21.7,9.7,21.7,21.7l0,0c0,12-9.7,21.7-21.7,21.7l0,0C419.7,173.2,409.9,163.5,409.9,151.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,34 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:18;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:19;stroke-linecap:round;stroke-miterlimit:10;}
</style>
<g>
<circle cx="259.2" cy="252.5" r="38.3"/>
<path d="M259.2,361.8c-60.3,0-109.3-49-109.3-109.3s49-109.3,109.3-109.3s109.3,49,109.3,109.3S319.4,361.8,259.2,361.8z
M259.2,171.8c-44.5,0-80.7,36.2-80.7,80.7s36.2,80.7,80.7,80.7s80.7-36.2,80.7-80.7S303.7,171.8,259.2,171.8z"/>
<path d="M414.1,268.5c-7.9,0-14.2-6.4-14.3-14.2c0-7.9,6.3-14.3,14.2-14.3c17.1-0.1,34.5-0.2,51.6-0.3c0,0,0.1,0,0.1,0
c7.8,0,14.2,6.3,14.3,14.2c0.1,7.9-6.3,14.3-14.2,14.4C448.8,268.3,431.4,268.4,414.1,268.5C414.2,268.5,414.1,268.5,414.1,268.5z"
/>
<path d="M105.8,268.5C105.8,268.5,105.8,268.5,105.8,268.5c-17.3-0.1-34.6-0.2-51.7-0.4c-7.9-0.1-14.2-6.5-14.1-14.4
c0.1-7.8,6.5-14.1,14.3-14.1c0.1,0,0.1,0,0.2,0c17,0.2,34.3,0.3,51.5,0.4c7.9,0,14.3,6.5,14.2,14.3
C120.1,262.2,113.7,268.5,105.8,268.5z"/>
<path d="M369.1,159.5c-3.6,0-7.3-1.4-10-4.1c-5.6-5.6-5.6-14.6-0.1-20.2c12-12.1,24.2-24.5,36.3-36.7c5.5-5.6,14.6-5.7,20.2-0.2
c5.6,5.5,5.7,14.6,0.2,20.2c-12.1,12.3-24.3,24.7-36.4,36.8C376.4,158.1,372.8,159.5,369.1,159.5z"/>
<path d="M114.4,413.7c-3.7,0-7.4-1.4-10.2-4.3c-5.5-5.6-5.4-14.7,0.2-20.2c12.2-11.9,24.5-24.1,36.7-36.1
c5.6-5.6,14.6-5.5,20.2,0.1c5.6,5.6,5.5,14.6-0.1,20.2c-12.2,12.1-24.6,24.3-36.8,36.2C121.6,412.3,118,413.7,114.4,413.7z"/>
<path d="M260.2,114.2c-7.9,0-14.2-6.4-14.3-14.2c-0.1-17-0.2-34.4-0.3-51.6c-0.1-7.9,6.3-14.3,14.2-14.4c0,0,0.1,0,0.1,0
c7.8,0,14.2,6.3,14.3,14.2c0.1,17.3,0.3,34.7,0.3,51.7C274.5,107.8,268.1,114.2,260.2,114.2C260.2,114.2,260.2,114.2,260.2,114.2z"
/>
<path d="M259.9,474.1c-0.1,0-0.1,0-0.2,0c-7.9-0.1-14.2-6.5-14.1-14.4c0.2-16.9,0.3-34.3,0.4-51.5c0-7.9,6.4-14.2,14.3-14.2
c0,0,0,0,0.1,0c7.9,0,14.3,6.5,14.2,14.3c-0.1,17.3-0.2,34.6-0.4,51.7C274,467.8,267.7,474.1,259.9,474.1z"/>
<path d="M151.2,159.3c-3.6,0-7.3-1.4-10-4.1c-12.2-12.1-24.6-24.3-36.7-36.3c-5.6-5.5-5.7-14.6-0.2-20.2c5.5-5.6,14.6-5.7,20.2-0.2
c12.2,12,24.6,24.2,36.8,36.4c5.6,5.6,5.6,14.6,0.1,20.2C158.5,157.9,154.9,159.3,151.2,159.3z"/>
<path d="M405.4,414c-3.7,0-7.4-1.4-10.2-4.3c-11.9-12.2-24-24.5-36.1-36.7c-5.6-5.6-5.5-14.6,0.1-20.2c5.6-5.6,14.6-5.5,20.2,0.1
c12.1,12.2,24.3,24.6,36.2,36.8c5.5,5.6,5.4,14.7-0.2,20.2C412.6,412.6,409,414,405.4,414z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,30 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:18;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:19;stroke-linecap:round;stroke-miterlimit:10;}
.st3{stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
</style>
<g>
<path d="M438,121.5c0-8-6.5-14.5-14.5-14.5h-333c-8,0-14.5,6.5-14.5,14.5v298c0,8,6.5,14.5,14.5,14.5h333c8,0,14.5-6.5,14.5-14.5
V121.5z M219.4,130H391c8.3,0,15,7.2,15,16s-6.7,16-15,16H219.4c-8.3,0-15-7.2-15-16S211.1,130,219.4,130z M171.5,128.6
c9.2,0,16.7,7.5,16.7,16.7c0,9.2-7.5,16.7-16.7,16.7c-9.2,0-16.7-7.5-16.7-16.7C154.8,136,162.3,128.6,171.5,128.6z M121,128.6
c9.2,0,16.7,7.5,16.7,16.7c0,9.2-7.5,16.7-16.7,16.7c-9.2,0-16.7-7.5-16.7-16.7C104.4,136,111.8,128.6,121,128.6z M412,405H104V186
h308V405z"/>
</g>
<g>
<path class="st3" d="M161.6,320.5h-14.3l-20.5-59.3h12.9l14.7,44.8l14.7-44.8h12.9l14.7,44.8l14.7-44.8h12.9L204,320.5h-14.3
l-14-40.6L161.6,320.5z"/>
<path class="st3" d="M299.4,296.3h-46.5c0.3,4.3,2.3,7.8,5.9,10.4c3.6,2.6,7.8,4,12.5,4c7.4,0,13.1-2.3,16.9-7l7.1,7.8
c-6.4,6.6-14.7,9.9-25,9.9c-8.3,0-15.4-2.8-21.2-8.3c-5.8-5.5-8.7-13-8.7-22.3c0-9.3,3-16.7,8.9-22.2c5.9-5.5,12.9-8.2,21-8.2
c8.1,0,14.9,2.4,20.6,7.3c5.6,4.9,8.5,11.6,8.5,20.1V296.3z M252.9,286.5h34c0-5-1.6-8.8-4.7-11.5s-7-4-11.5-4
c-4.6,0-8.7,1.4-12.3,4.2C254.7,278,252.9,281.8,252.9,286.5z"/>
<path class="st3" d="M353.6,260.3c7.9,0,14.7,2.8,20.4,8.2c5.6,5.5,8.5,12.8,8.5,22c0,9.1-2.8,16.6-8.4,22.3
c-5.6,5.7-12.1,8.6-19.6,8.6s-14.2-3.3-20.1-9.8v8.9h-12.5v-82.7h12.5v33.8C339.2,264.1,345.6,260.3,353.6,260.3z M334.1,291
c0,5.6,1.7,10.3,5.1,13.9c3.4,3.6,7.6,5.4,12.5,5.4c4.9,0,9.2-1.8,12.8-5.3c3.6-3.6,5.5-8.2,5.5-13.9c0-5.7-1.8-10.4-5.3-14.2
c-3.6-3.8-7.8-5.6-12.8-5.6c-5,0-9.2,1.9-12.6,5.6C335.8,280.6,334.1,285.3,334.1,291z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,29 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path d="M331.8,283.4c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C342.1,306.6,331.8,296.2,331.8,283.4z"/>
<path d="M277.8,350.9c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C288.1,374.2,277.8,363.8,277.8,350.9z"/>
<path d="M216.3,368.8c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C226.7,392,216.3,381.6,216.3,368.8z"/>
<path d="M169.9,308.9c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C180.3,332.1,169.9,321.7,169.9,308.9z"/>
<path d="M251.2,447.4c-4.9-3.6-8.3-9.1-9.2-15.3c-0.9-6,0.6-12.3,4.2-17.2c3.6-4.9,9.1-8.3,15.2-9.1c6-0.9,12.3,0.6,17.3,4.3
c4.9,3.6,8.3,9.1,9.1,15.2c0.9,6-0.6,12.3-4.2,17.2s-9.1,8.3-15.2,9.1C262.4,452.6,256.1,451,251.2,447.4z"/>
<path d="M67.6,246.1c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C78,269.3,67.6,258.8,67.6,246.1z"/>
<path d="M178.8,199.5c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C189.1,222.7,178.8,212.2,178.8,199.5z"/>
<path d="M250.3,293.9c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C260.7,317.1,250.3,306.6,250.3,293.9z"/>
<path d="M413,242.1c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C423.5,265.3,413,255,413,242.1z"/>
<path d="M302.1,203.7c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C312.6,226.9,302.1,216.5,302.1,203.7z"/>
<path d="M132.3,113.5c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C142.8,136.6,132.3,126.2,132.3,113.5z"/>
<path d="M366.6,136.7c0-12.8,10.4-23.2,23.2-23.2l0,0c12.8,0,23.2,10.4,23.2,23.2l0,0c0,12.8-10.4,23.2-23.2,23.2l0,0
C377.1,159.9,366.6,149.5,366.6,136.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,20 @@
<?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 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path d="M494.2,117.2c-2.2-5.3-7.8-8.4-13.5-7.3l-89.9,16.4l44.9-72.6c2.8-4.6,2.3-10.5-1.2-14.5s-9.3-5.3-14.2-3l-213.1,98
c-15.3-4.9-72.2-27.3-111.4-43.2c-0.3-0.1-0.6-0.2-0.8-0.3c0,0-0.1,0-0.1,0c-0.4-0.1-0.9-0.2-1.3-0.3c-0.1,0-0.2-0.1-0.3-0.1
c-0.5-0.1-0.9-0.2-1.4-0.2c0,0,0,0-0.1,0c-0.5,0-0.9,0.1-1.4,0.1c-0.1,0-0.1,0-0.2,0c0,0-0.1,0-0.1,0c-0.5,0-0.9,0.1-1.3,0.2
c-0.1,0-0.2,0-0.3,0.1c-0.4,0.1-0.8,0.2-1.2,0.3c-0.1,0-0.1,0-0.2,0.1c-0.4,0.1-0.8,0.3-1.2,0.5c-0.1,0-0.2,0.1-0.3,0.1
c-0.8,0.4-1.6,0.9-2.3,1.5c-0.1,0.1-0.2,0.1-0.2,0.2c-0.3,0.3-0.7,0.6-1,0.9c0,0-0.1,0.1-0.1,0.1c-0.2,0.2-0.4,0.4-0.5,0.6
c-0.1,0.1-0.2,0.2-0.3,0.4c-0.1,0.1-0.1,0.2-0.2,0.3c-0.3,0.4-0.5,0.8-0.7,1.2c0,0,0,0,0,0l-27.3,52.1l-32.7,40.1
c-3.4,4.2-3.7,10.2-0.6,14.7c2.3,3.3,6.1,5.2,10,5.2c1.3,0,2.6-0.2,3.9-0.7l50.1-17l40.6-1.9l-26.7,50.3c-2.4,4.5-1.7,9.9,1.6,13.7
c0.3,0.3,25.6,29.3,51.7,57.7c15.4,16.8,28,30.1,37.6,39.6c6.6,6.5,11.6,11.2,15.6,14.4l-16,61l-45.7,18.2
c-6.3,2.5-9.3,9.6-6.8,15.8c1.9,4.8,6.5,7.7,11.3,7.7c1.5,0,3-0.3,4.5-0.9l44.5-17.7l17.2,15.4c2.3,2.1,5.2,3.1,8.1,3.1
c3.4,0,6.7-1.4,9.1-4.1c4.5-5,4-12.7-1-17.2L212.1,431l16-60.7l75.7,89.3c2.4,2.8,5.8,4.3,9.3,4.3c1.1,0,2.1-0.1,3.2-0.4l84.6-22.8
c4.8-1.3,8.4-5.4,8.9-10.4c0.6-5-2-9.8-6.4-12.1l-136.2-71.8l44.4-91.2L489.9,132C494.6,128.7,496.4,122.6,494.2,117.2z
M294.4,234.2l-122.6-55.4l41.5-20.6c0,0,0,0,0,0l180.4-82.9L294.4,234.2z M146.4,160.8l-24.8-33.2c16.7,6.6,39.3,15.5,54,21
L146.4,160.8z M87.3,166.6l-9.6-12.3l15.1-28.9l27.4,37.3l1.7,2.3L87.3,166.6z M123.2,243.6l22.8-43.3c18.3,44.8,35.2,91,47,121
C173.7,301.1,147.7,271.5,123.2,243.6z M317.4,438l-57.5-67.8l104.6,55.1L317.4,438z M223.3,336.7c-5.5-14.1-42.6-104.1-30.8-76.4
c0.6,1.4-20.5-49.2-27.9-66.3l120.9,58.9l-47.1,95L223.3,336.7z M330.5,216.7l43-62.3l70.9-16.2L330.5,216.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,108 @@
<?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 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.9;}
.st1{fill:#FFFFFF;}
.st2{fill:#1E1E1E;}
.st3{fill:#EAEAEA;}
.st4{opacity:0.47;}
.st5{opacity:0.47;fill:#EAEAEA;}
</style>
<g>
<g class="st0">
<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 class="st0">
<path class="st2" d="M50,96c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V54c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96z"/>
</g>
<path d="M32.7,9.4c-4.3-4.3-11.2-4.3-15.5,0c-2.1,2.1-3.2,4.8-3.2,7.7c0,2.9,1.1,5.7,3.2,7.7C19.4,27,22.2,28,25,28
c2.8,0,5.6-1.1,7.7-3.2c2.1-2.1,3.2-4.8,3.2-7.7C35.9,14.2,34.8,11.4,32.7,9.4z M18.3,10.4c1.4-1.4,3.2-2.3,5-2.6
C23,8.3,22.7,9,22.4,9.9c-0.6,2-1,4.6-1,7.3c0,0.1,0,0.3,0,0.5c0.3,0.1,0.6,0.1,0.9,0.1c0.1,0,0.2,0,0.3,0c0-0.2,0-0.4,0-0.6
c0-5.5,1.4-9.2,2.5-9.5c0,0,0,0,0.1-0.1c2.4,0,4.7,1,6.5,2.8c1.7,1.7,2.7,4,2.8,6.5c-0.2,1.1-3.9,2.5-9.5,2.5
c-5.5,0-9.1-1.4-9.4-2.4C15.6,14.5,16.6,12.2,18.3,10.4z M31.7,23.8c-1.9,1.9-4.3,2.8-6.8,2.8c-0.7-0.4-1.4-2.1-1.9-4.6l-1.2-0.1
c0.2,0.9,0.4,1.8,0.6,2.6c0.2,0.8,0.5,1.4,0.8,1.9c-1.8-0.3-3.5-1.2-4.9-2.6c-1.4-1.4-2.3-3.2-2.6-5.1c0.5,0.3,1.2,0.6,2,0.9
c2,0.6,4.6,1,7.3,1s5.3-0.3,7.3-1c0.8-0.3,1.5-0.6,2-0.9C34,20.6,33.1,22.4,31.7,23.8z"/>
<g>
<path d="M11.1,37.5c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2c-0.2-0.1-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2s0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3c0.3,0.1,0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.2-0.3,0.4-0.5,0.6s-0.5,0.3-0.8,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
s-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2c-0.2-0.1-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3S7.7,39,7.6,38.9c-0.2-0.1-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2c0.4,0.1,0.7,0.3,1,0.5L11.1,37.5z"/>
<path d="M13.1,42.2v-6.4h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H13.1z M14.3,39h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4c0-0.1-0.1-0.2-0.2-0.3C16.2,37.1,16.1,37,16,37
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V39z"/>
<path d="M24.3,35.8v6.4H23v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H23v-2.6H24.3z"/>
<path d="M30.3,41.1v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H30.3z"/>
<path d="M31.5,42.2v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4L34,40.1h-1.2v2.1H31.5z M32.7,39h1.6c0.1,0,0.2,0,0.3-0.1
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V39z"/>
<path d="M42.2,41.1v1.1h-4.4v-6.4h4.4v1.1H39v1.5h2.7v1H39v1.7H42.2z"/>
</g>
<path class="st3" d="M33.1,59.4c-4.3-4.3-11.2-4.3-15.5,0c-2.1,2.1-3.2,4.8-3.2,7.7c0,2.9,1.1,5.7,3.2,7.7c2.1,2.1,4.9,3.2,7.7,3.2
c2.8,0,5.6-1.1,7.7-3.2c2.1-2.1,3.2-4.8,3.2-7.7C36.3,64.2,35.1,61.5,33.1,59.4z M18.6,60.4c1.4-1.4,3.2-2.3,5-2.6
c-0.3,0.5-0.7,1.2-0.9,2.1c-0.6,2-1,4.6-1,7.3c0,0.1,0,0.3,0,0.5c0.3,0.1,0.6,0.1,0.9,0.1c0.1,0,0.2,0,0.3,0c0-0.2,0-0.4,0-0.6
c0-5.5,1.4-9.2,2.5-9.5c0,0,0,0,0.1-0.1c2.4,0,4.7,1,6.5,2.8c1.7,1.7,2.7,4,2.8,6.5c-0.2,1.1-3.9,2.5-9.5,2.5
c-5.5,0-9.1-1.4-9.4-2.4C15.9,64.5,16.9,62.2,18.6,60.4z M32,73.8c-1.9,1.9-4.3,2.8-6.8,2.8c-0.7-0.4-1.4-2.1-1.9-4.6l-1.2-0.1
c0.2,0.9,0.4,1.8,0.6,2.6c0.2,0.8,0.5,1.4,0.8,1.9c-1.8-0.3-3.5-1.2-4.9-2.6c-1.4-1.4-2.3-3.2-2.6-5.1c0.5,0.3,1.2,0.6,2,0.9
c2,0.6,4.6,1,7.3,1s5.3-0.3,7.3-1c0.8-0.3,1.5-0.6,2-0.9C34.3,70.7,33.4,72.4,32,73.8z"/>
<g>
<path class="st3" d="M11.5,87.5c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2S10.5,87,10.3,87c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2s-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3C8.9,88,9,88.1,9.1,88.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3s0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.2-0.3,0.4-0.5,0.6c-0.2,0.2-0.5,0.3-0.8,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
C8,92,7.6,91.8,7.2,91.5l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2c-0.2-0.1-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3s-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4s0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2s0.7,0.3,1,0.5L11.5,87.5z"/>
<path class="st3" d="M13.4,92.2v-6.4h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5s0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H13.4z M14.7,89h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4c0-0.1-0.1-0.2-0.2-0.3s-0.2-0.2-0.3-0.2
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V89z"/>
<path class="st3" d="M24.6,85.9v6.4h-1.2v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6h2.9v-2.6H24.6z"/>
<path class="st3" d="M30.6,91.2v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H30.6z"/>
<path class="st3" d="M31.8,92.2v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.4-0.1,0.8-0.3,1.1s-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31.8z M33.1,89h1.6c0.1,0,0.2,0,0.3-0.1
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3S35,87.1,34.9,87
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V89z"/>
<path class="st3" d="M42.5,91.2v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5H42v1h-2.7v1.7H42.5z"/>
</g>
<g class="st4">
<path class="st2" d="M50,146c0,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,4V146z"/>
</g>
<path class="st5" d="M33.1,109.4c-4.3-4.3-11.2-4.3-15.5,0c-2.1,2.1-3.2,4.8-3.2,7.7c0,2.9,1.1,5.7,3.2,7.7c2.1,2.1,4.9,3.2,7.7,3.2
c2.8,0,5.6-1.1,7.7-3.2c2.1-2.1,3.2-4.8,3.2-7.7C36.3,114.2,35.1,111.5,33.1,109.4z M18.6,110.4c1.4-1.4,3.2-2.3,5-2.6
c-0.3,0.5-0.7,1.2-0.9,2.1c-0.6,2-1,4.6-1,7.3c0,0.1,0,0.3,0,0.5c0.3,0.1,0.6,0.1,0.9,0.1c0.1,0,0.2,0,0.3,0c0-0.2,0-0.4,0-0.6
c0-5.5,1.4-9.2,2.5-9.5c0,0,0,0,0.1-0.1c2.4,0,4.7,1,6.5,2.8c1.7,1.7,2.7,4,2.8,6.5c-0.2,1.1-3.9,2.5-9.5,2.5
c-5.5,0-9.1-1.4-9.4-2.4C15.9,114.5,16.9,112.2,18.6,110.4z M32,123.8c-1.9,1.9-4.3,2.8-6.8,2.8c-0.7-0.4-1.4-2.1-1.9-4.6l-1.2-0.1
c0.2,0.9,0.4,1.8,0.6,2.6c0.2,0.8,0.5,1.4,0.8,1.9c-1.8-0.3-3.5-1.2-4.9-2.6c-1.4-1.4-2.3-3.2-2.6-5.1c0.5,0.3,1.2,0.6,2,0.9
c2,0.6,4.6,1,7.3,1s5.3-0.3,7.3-1c0.8-0.3,1.5-0.6,2-0.9C34.3,120.7,33.4,122.4,32,123.8z"/>
<g class="st4">
<path class="st3" d="M11.5,137.5c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2s-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2s-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3s0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.2-0.3,0.4-0.5,0.6c-0.2,0.2-0.5,0.3-0.8,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2c-0.2-0.1-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3s-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4s0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2s0.7,0.3,1,0.5L11.5,137.5z"/>
<path class="st3" d="M13.4,142.2v-6.4h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5s0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H13.4z M14.7,139h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4c0-0.1-0.1-0.2-0.2-0.3s-0.2-0.2-0.3-0.2
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V139z"/>
<path class="st3" d="M24.6,135.9v6.4h-1.2v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6h2.9v-2.6H24.6z"/>
<path class="st3" d="M30.6,141.2v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H30.6z"/>
<path class="st3" d="M31.8,142.2v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.4-0.1,0.8-0.3,1.1s-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31.8z M33.1,139h1.6c0.1,0,0.2,0,0.3-0.1
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3s-0.2-0.2-0.3-0.2
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V139z"/>
<path class="st3" d="M42.5,141.2v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5H42v1h-2.7v1.7H42.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -1,10 +1,13 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 1.0 import QtQuick.Controls 1.4
import QtWebEngine 1.1 import QtWebEngine 1.1
import QtWebChannel 1.0 import QtWebChannel 1.0
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import "../../controls" import "../../controls"
import "../toolbars"
import HFWebEngineProfile 1.0 import HFWebEngineProfile 1.0
import QtGraphicalEffects 1.0
import "../../styles-uit"
StackView { StackView {
id: editRoot id: editRoot
@ -33,109 +36,222 @@ StackView {
height: 60 height: 60
Tab { Tab {
title: "Create" title: "CREATE"
active: true active: true
enabled: true enabled: true
property string originalUrl: "" property string originalUrl: ""
Flow { Rectangle {
id: createEntitiesFlow color: "#404040"
spacing: 16
Button { Text {
iconSource: "../../../../../scripts/system/assets/images/tools/assets-01.svg" color: "#ffffff"
text: "ASSETS" text: "Choose an Entity Type to Create:"
onClicked: { font.pixelSize: 14
editRoot.sendToScript({ font.bold: true
method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" } anchors.top: parent.top
}); anchors.topMargin: 28
anchors.left: parent.left
anchors.leftMargin: 28
}
Flow {
id: createEntitiesFlow
spacing: 35
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: parent.top
anchors.topMargin: 70
NewEntityButton {
icon: "icons/create-icons/94-model-01.svg"
text: "MODEL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/21-cube-01.svg"
text: "CUBE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/22-sphere-01.svg"
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/24-light-01.svg"
text: "LIGHT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/20-text-01.svg"
text: "TEXT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/25-web-1-01.svg"
text: "WEB"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/23-zone-01.svg"
text: "ZONE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/90-particles-01.svg"
text: "PARTICLE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
});
editTabView.currentIndex = 2
}
} }
} }
Button { Item {
iconSource: "../../../../../scripts/system/assets/images/tools/model-01.svg" id: assetServerButton
text: "MODEL" width: 370
onClicked: { height: 38
editRoot.sendToScript({ anchors.horizontalCenter: parent.horizontalCenter
method: "newEntityButtonClicked", params: { buttonName: "newModelButton" } anchors.top: createEntitiesFlow.bottom
}); anchors.topMargin: 35
Rectangle {
id: assetServerButtonBg
color: "black"
opacity: 1
radius: 6
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
} }
}
Button { Rectangle {
iconSource: "../../../../../scripts/system/assets/images/tools/cube-01.svg" id: assetServerButtonGradient
text: "CUBE" gradient: Gradient {
onClicked: { GradientStop {
editRoot.sendToScript({ position: 0
method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" } color: "#383838"
}); }
}
}
Button { GradientStop {
iconSource: "../../../../../scripts/system/assets/images/tools/sphere-01.svg" position: 1
text: "SPHERE" color: "black"
onClicked: { }
editRoot.sendToScript({ }
method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" } opacity: 1
}); radius: 6
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
} }
}
Button { Text {
iconSource: "../../../../../scripts/system/assets/images/tools/light-01.svg" color: "#ffffff"
text: "LIGHT" text: "OPEN THIS DOMAIN'S ASSET SERVER"
onClicked: { font.bold: true
editRoot.sendToScript({ font.pixelSize: 14
method: "newEntityButtonClicked", params: { buttonName: "newLightButton" } anchors.centerIn: parent
});
} }
}
Button { MouseArea {
iconSource: "../../../../../scripts/system/assets/images/tools/text-01.svg" anchors.fill: parent
text: "TEXT" hoverEnabled: true
onClicked: { enabled: true
editRoot.sendToScript({ onClicked: {
method: "newEntityButtonClicked", params: { buttonName: "newTextButton" } editRoot.sendToScript({
}); method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
});
}
onEntered: {
assetServerButton.state = "hover state";
}
onExited: {
assetServerButton.state = "base state";
}
} }
}
Button { states: [
iconSource: "../../../../../scripts/system/assets/images/tools/web-01.svg" State {
text: "WEB" name: "hover state"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
});
}
}
Button { PropertyChanges {
iconSource: "../../../../../scripts/system/assets/images/tools/zone-01.svg" target: assetServerButtonGradient
text: "ZONE" opacity: 0
onClicked: { }
editRoot.sendToScript({ },
method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" } State {
}); name: "base state"
}
}
Button { PropertyChanges {
iconSource: "../../../../../scripts/system/assets/images/tools/particle-01.svg" target: assetServerButtonGradient
text: "PARTICLE" opacity: 1
onClicked: { }
editRoot.sendToScript({ }
method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } ]
});
}
} }
} }
} }
Tab { Tab {
title: "List" title: "LIST"
active: true active: true
enabled: true enabled: true
property string originalUrl: "" property string originalUrl: ""
@ -150,7 +266,7 @@ StackView {
} }
Tab { Tab {
title: "Properties" title: "PROPERTIES"
active: true active: true
enabled: true enabled: true
property string originalUrl: "" property string originalUrl: ""
@ -165,7 +281,7 @@ StackView {
} }
Tab { Tab {
title: "Grid" title: "GRID"
active: true active: true
enabled: true enabled: true
property string originalUrl: "" property string originalUrl: ""
@ -180,7 +296,7 @@ StackView {
} }
Tab { Tab {
title: "Particles" title: "P"
active: true active: true
enabled: true enabled: true
property string originalUrl: "" property string originalUrl: ""
@ -198,17 +314,40 @@ StackView {
style: TabViewStyle { style: TabViewStyle {
frameOverlap: 1 frameOverlap: 1
tab: Rectangle { tab: Rectangle {
color: styleData.selected ? "slategrey" :"grey" color: styleData.selected ? "#404040" :"black"
implicitWidth: text.width + 25 implicitWidth: text.width + 42
implicitHeight: 60 implicitHeight: 40
radius: 2
Text { Text {
id: text id: text
anchors.centerIn: parent anchors.centerIn: parent
text: styleData.title text: styleData.title
font.pixelSize: 16
font.bold: true
color: styleData.selected ? "white" : "white" color: styleData.selected ? "white" : "white"
property string glyphtext: ""
HiFiGlyphs {
anchors.centerIn: parent
size: 30
color: "#ffffff"
text: text.glyphtext
}
Component.onCompleted: if (styleData.title == "P") {
text.text = " ";
text.glyphtext = "\ue004";
}
} }
} }
tabBar: Rectangle {
color: "black"
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
}
} }
} }
} }

View file

@ -0,0 +1,160 @@
import QtQuick 2.0
import QtGraphicalEffects 1.0
Item {
id: newEntityButton
property var uuid;
property string text: "ENTITY"
property string icon: "icons/edit-icon.svg"
property string activeText: newEntityButton.text
property string activeIcon: newEntityButton.icon
property bool isActive: false
property bool inDebugMode: false
property bool isEntered: false
property double sortOrder: 100
property int stableOrder: 0
property var tabletRoot;
width: 100
height: 100
signal clicked()
function changeProperty(key, value) {
tabletButton[key] = value;
}
onIsActiveChanged: {
if (tabletButton.isEntered) {
tabletButton.state = (tabletButton.isActive) ? "hover active state" : "hover sate";
} else {
tabletButton.state = (tabletButton.isActive) ? "active state" : "base sate";
}
}
Rectangle {
id: buttonBg
color: "#1c1c1c"
opacity: 1
radius: 8
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
}
function urlHelper(src) {
if (src.match(/\bhttp/)) {
return src;
} else {
return "../../../" + src;
}
}
Rectangle {
id: buttonOutline
color: "#00000000"
opacity: 0
radius: 8
z: 1
border.width: 2
border.color: "#ffffff"
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
}
DropShadow {
id: glow
visible: false
anchors.fill: parent
horizontalOffset: 0
verticalOffset: 0
color: "#ffffff"
radius: 20
z: -1
samples: 41
source: buttonOutline
}
Image {
id: icon
width: 50
height: 50
visible: false
anchors.bottom: text.top
anchors.bottomMargin: 5
anchors.horizontalCenter: parent.horizontalCenter
fillMode: Image.Stretch
source: newEntityButton.urlHelper(newEntityButton.icon)
}
ColorOverlay {
id: iconColorOverlay
anchors.fill: icon
source: icon
color: "#ffffff"
}
Text {
id: text
color: "#ffffff"
text: newEntityButton.text
font.bold: true
font.pixelSize: 16
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: true
onClicked: {
newEntityButton.clicked();
}
onEntered: {
newEntityButton.state = "hover state";
}
onExited: {
newEntityButton.state = "base state";
}
}
states: [
State {
name: "hover state"
PropertyChanges {
target: buttonOutline
opacity: 1
}
PropertyChanges {
target: glow
visible: true
}
},
State {
name: "base state"
PropertyChanges {
target: glow
visible: false
}
}
]
}

View file

@ -313,6 +313,6 @@ Fadable {
} }
} }
onMouseEntered: console.log("Mouse entered " + window) // onMouseEntered: console.log("Mouse entered " + window)
onMouseExited: console.log("Mouse exited " + window) // onMouseExited: console.log("Mouse exited " + window)
} }

View file

@ -227,8 +227,7 @@ void MyAvatar::simulateAttachments(float deltaTime) {
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation() // don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
} }
QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
bool distanceAdjust, glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut) {
CameraMode mode = qApp->getCamera()->getMode(); CameraMode mode = qApp->getCamera()->getMode();
_globalPosition = getPosition(); _globalPosition = getPosition();
_globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius();
@ -239,12 +238,12 @@ QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTi
// fake the avatar position that is sent up to the AvatarMixer // fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = getPosition(); glm::vec3 oldPosition = getPosition();
setPosition(getSkeletonPosition()); setPosition(getSkeletonPosition());
QByteArray array = AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut); QByteArray array = AvatarData::toByteArrayStateful(dataDetail);
// copy the correct position back // copy the correct position back
setPosition(oldPosition); setPosition(oldPosition);
return array; return array;
} }
return AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut); return AvatarData::toByteArrayStateful(dataDetail);
} }
void MyAvatar::centerBody() { void MyAvatar::centerBody() {

View file

@ -338,8 +338,7 @@ private:
glm::quat getWorldBodyOrientation() const; glm::quat getWorldBodyOrientation() const;
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector<JointData>* sentJointDataOut = nullptr) override;
void simulate(float deltaTime); void simulate(float deltaTime);
void updateFromTrackers(float deltaTime); void updateFromTrackers(float deltaTime);

View file

@ -120,10 +120,6 @@ void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) {
updateAttitude(); updateAttitude();
} }
float AvatarData::getTargetScale() const {
return _targetScale;
}
void AvatarData::setTargetScale(float targetScale) { void AvatarData::setTargetScale(float targetScale) {
auto newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); auto newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
if (_targetScale != newValue) { if (_targetScale != newValue) {
@ -141,10 +137,10 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) {
_handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition()); _handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition());
} }
void AvatarData::lazyInitHeadData() { void AvatarData::lazyInitHeadData() const {
// lazily allocate memory for HeadData in case we're not an Avatar instance // lazily allocate memory for HeadData in case we're not an Avatar instance
if (!_headData) { if (!_headData) {
_headData = new HeadData(this); _headData = new HeadData(const_cast<AvatarData*>(this));
} }
if (_forceFaceTrackerConnected) { if (_forceFaceTrackerConnected) {
_headData->_isFaceTrackerConnected = true; _headData->_isFaceTrackerConnected = true;
@ -152,39 +148,7 @@ void AvatarData::lazyInitHeadData() {
} }
bool AvatarData::avatarBoundingBoxChangedSince(quint64 time) { float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const {
return _avatarBoundingBoxChanged >= time;
}
bool AvatarData::avatarScaleChangedSince(quint64 time) {
return _avatarScaleChanged >= time;
}
bool AvatarData::lookAtPositionChangedSince(quint64 time) {
return _headData->lookAtPositionChangedSince(time);
}
bool AvatarData::audioLoudnessChangedSince(quint64 time) {
return _headData->audioLoudnessChangedSince(time);
}
bool AvatarData::sensorToWorldMatrixChangedSince(quint64 time) {
return _sensorToWorldMatrixChanged >= time;
}
bool AvatarData::additionalFlagsChangedSince(quint64 time) {
return _additionalFlagsChanged >= time;
}
bool AvatarData::parentInfoChangedSince(quint64 time) {
return _parentChanged >= time;
}
bool AvatarData::faceTrackerInfoChangedSince(quint64 time) {
return true; // FIXME!
}
float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) {
auto distance = glm::distance(_globalPosition, viewerPosition); auto distance = glm::distance(_globalPosition, viewerPosition);
float result = ROTATION_CHANGE_179D; // assume worst float result = ROTATION_CHANGE_179D; // assume worst
if (distance < AVATAR_DISTANCE_LEVEL_1) { if (distance < AVATAR_DISTANCE_LEVEL_1) {
@ -199,20 +163,24 @@ float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) {
return result; return result;
} }
float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) { float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) const {
return AVATAR_MIN_TRANSLATION; // Eventually make this distance sensitive as well return AVATAR_MIN_TRANSLATION; // Eventually make this distance sensitive as well
} }
QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, // we want to track outbound data in this case...
bool distanceAdjust, glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut) { QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail) {
AvatarDataPacket::HasFlags hasFlagsOut;
auto lastSentTime = _lastToByteArray;
_lastToByteArray = usecTimestampNow();
return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(),
hasFlagsOut, false, false, glm::vec3(0), nullptr,
&_outboundDataRate);
}
// if no timestamp was included, then assume the avatarData is single instance QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
// and is tracking its own last encoding time. AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust,
if (lastSentTime == 0) { glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const {
lastSentTime = _lastToByteArray;
_lastToByteArray = usecTimestampNow();
}
bool cullSmallChanges = (dataDetail == CullSmallData); bool cullSmallChanges = (dataDetail == CullSmallData);
bool sendAll = (dataDetail == SendAllData); bool sendAll = (dataDetail == SendAllData);
@ -259,26 +227,26 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
// separately // separately
bool hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); bool hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
bool hasAvatarLocalPosition = hasParent() && (sendAll || bool hasAvatarLocalPosition = hasParent() && (sendAll ||
tranlationChangedSince(lastSentTime) || tranlationChangedSince(lastSentTime) ||
parentInfoChangedSince(lastSentTime)); parentInfoChangedSince(lastSentTime));
bool hasFaceTrackerInfo = hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); bool hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime));
bool hasJointData = sendAll || !sendMinimum; bool hasJointData = sendAll || !sendMinimum;
// Leading flags, to indicate how much data is actually included in the packet... // Leading flags, to indicate how much data is actually included in the packet...
AvatarDataPacket::HasFlags packetStateFlags = AvatarDataPacket::HasFlags packetStateFlags =
(hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0)
| (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0)
| (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0)
| (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0)
| (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0)
| (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0)
| (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0)
| (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0)
| (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0)
| (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0)
| (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0)
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0); | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0);
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
destinationBuffer += sizeof(packetStateFlags); destinationBuffer += sizeof(packetStateFlags);
@ -293,7 +261,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_globalPositionRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->globalPositionRate.increment(numBytes);
}
} }
if (hasAvatarBoundingBox) { if (hasAvatarBoundingBox) {
@ -311,7 +281,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_avatarBoundingBoxRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->avatarBoundingBoxRate.increment(numBytes);
}
} }
if (hasAvatarOrientation) { if (hasAvatarOrientation) {
@ -320,7 +292,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_avatarOrientationRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->avatarOrientationRate.increment(numBytes);
}
} }
if (hasAvatarScale) { if (hasAvatarScale) {
@ -331,7 +305,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::AvatarScale); destinationBuffer += sizeof(AvatarDataPacket::AvatarScale);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_avatarScaleRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->avatarScaleRate.increment(numBytes);
}
} }
if (hasLookAtPosition) { if (hasLookAtPosition) {
@ -344,7 +320,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_lookAtPositionRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->lookAtPositionRate.increment(numBytes);
}
} }
if (hasAudioLoudness) { if (hasAudioLoudness) {
@ -354,7 +332,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness); destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_audioLoudnessRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->audioLoudnessRate.increment(numBytes);
}
} }
if (hasSensorToWorldMatrix) { if (hasSensorToWorldMatrix) {
@ -370,7 +350,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_sensorToWorldRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->sensorToWorldRate.increment(numBytes);
}
} }
if (hasAdditionalFlags) { if (hasAdditionalFlags) {
@ -403,7 +385,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_additionalFlagsRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->additionalFlagsRate.increment(numBytes);
}
} }
if (hasParentInfo) { if (hasParentInfo) {
@ -415,7 +399,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); destinationBuffer += sizeof(AvatarDataPacket::ParentInfo);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_parentInfoRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->parentInfoRate.increment(numBytes);
}
} }
if (hasAvatarLocalPosition) { if (hasAvatarLocalPosition) {
@ -428,7 +414,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_localPositionRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->localPositionRate.increment(numBytes);
}
} }
// If it is connected, pack up the data // If it is connected, pack up the data
@ -448,7 +436,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_faceTrackerRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->faceTrackerRate.increment(numBytes);
}
} }
// If it is connected, pack up the data // If it is connected, pack up the data
@ -540,9 +530,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
if (data.translationSet) { if (data.translationSet) {
validity |= (1 << validityBit); validity |= (1 << validityBit);
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
translationSentCount++; translationSentCount++;
#endif #endif
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
@ -592,24 +582,25 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
if (sendAll) { if (sendAll) {
qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll
<< "rotations:" << rotationSentCount << "translations:" << translationSentCount << "rotations:" << rotationSentCount << "translations:" << translationSentCount
<< "largest:" << maxTranslationDimension << "largest:" << maxTranslationDimension
<< "size:" << "size:"
<< (beforeRotations - startPosition) << "+" << (beforeRotations - startPosition) << "+"
<< (beforeTranslations - beforeRotations) << "+" << (beforeTranslations - beforeRotations) << "+"
<< (destinationBuffer - beforeTranslations) << "=" << (destinationBuffer - beforeTranslations) << "="
<< (destinationBuffer - startPosition); << (destinationBuffer - startPosition);
} }
#endif #endif
int numBytes = destinationBuffer - startSection; int numBytes = destinationBuffer - startSection;
_jointDataRateOutbound.increment(numBytes); if (outboundDataRateOut) {
outboundDataRateOut->jointDataRate.increment(numBytes);
}
} }
int avatarDataSize = destinationBuffer - startPosition; int avatarDataSize = destinationBuffer - startPosition;
return avatarDataByteArray.left(avatarDataSize); return avatarDataByteArray.left(avatarDataSize);
} }
// NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation
void AvatarData::doneEncoding(bool cullSmallChanges) { void AvatarData::doneEncoding(bool cullSmallChanges) {
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData. // The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
@ -1089,29 +1080,29 @@ float AvatarData::getDataRate(const QString& rateName) const {
} else if (rateName == "jointData") { } else if (rateName == "jointData") {
return _jointDataRate.rate() / BYTES_PER_KILOBIT; return _jointDataRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "globalPositionOutbound") { } else if (rateName == "globalPositionOutbound") {
return _globalPositionRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "localPositionOutbound") { } else if (rateName == "localPositionOutbound") {
return _localPositionRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.localPositionRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "avatarBoundingBoxOutbound") { } else if (rateName == "avatarBoundingBoxOutbound") {
return _avatarBoundingBoxRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.avatarBoundingBoxRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "avatarOrientationOutbound") { } else if (rateName == "avatarOrientationOutbound") {
return _avatarOrientationRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.avatarOrientationRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "avatarScaleOutbound") { } else if (rateName == "avatarScaleOutbound") {
return _avatarScaleRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.avatarScaleRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "lookAtPositionOutbound") { } else if (rateName == "lookAtPositionOutbound") {
return _lookAtPositionRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.lookAtPositionRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "audioLoudnessOutbound") { } else if (rateName == "audioLoudnessOutbound") {
return _audioLoudnessRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.audioLoudnessRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "sensorToWorkMatrixOutbound") { } else if (rateName == "sensorToWorkMatrixOutbound") {
return _sensorToWorldRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.sensorToWorldRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "additionalFlagsOutbound") { } else if (rateName == "additionalFlagsOutbound") {
return _additionalFlagsRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.additionalFlagsRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "parentInfoOutbound") { } else if (rateName == "parentInfoOutbound") {
return _parentInfoRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.parentInfoRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "faceTrackerOutbound") { } else if (rateName == "faceTrackerOutbound") {
return _faceTrackerRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.faceTrackerRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointDataOutbound") { } else if (rateName == "jointDataOutbound") {
return _jointDataRateOutbound.rate() / BYTES_PER_KILOBIT; return _outboundDataRate.jointDataRate.rate() / BYTES_PER_KILOBIT;
} }
return 0.0f; return 0.0f;
} }
@ -1445,7 +1436,7 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide
} }
static const QUrl emptyURL(""); static const QUrl emptyURL("");
const QUrl& AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) { QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
// We don't put file urls on the wire, but instead convert to empty. // We don't put file urls on the wire, but instead convert to empty.
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
} }
@ -1483,7 +1474,7 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
} }
} }
QByteArray AvatarData::identityByteArray() { QByteArray AvatarData::identityByteArray() const {
QByteArray identityData; QByteArray identityData;
QDataStream identityStream(&identityData, QIODevice::Append); QDataStream identityStream(&identityData, QIODevice::Append);
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL);
@ -1646,13 +1637,7 @@ void AvatarData::sendAvatarDataPacket() {
bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
auto dataDetail = cullSmallData ? SendAllData : CullSmallData; auto dataDetail = cullSmallData ? SendAllData : CullSmallData;
QVector<JointData> lastSentJointData; QByteArray avatarByteArray = toByteArrayStateful(dataDetail);
{
QReadLocker readLock(&_jointDataLock);
_lastSentJointData.resize(_jointData.size());
lastSentJointData = _lastSentJointData;
}
QByteArray avatarByteArray = toByteArray(dataDetail, 0, lastSentJointData);
doneEncoding(cullSmallData); doneEncoding(cullSmallData);
static AvatarDataSequenceNumber sequenceNumber = 0; static AvatarDataSequenceNumber sequenceNumber = 0;

View file

@ -288,6 +288,23 @@ class AttachmentData;
class Transform; class Transform;
using TransformPointer = std::shared_ptr<Transform>; using TransformPointer = std::shared_ptr<Transform>;
class AvatarDataRate {
public:
RateCounter<> globalPositionRate;
RateCounter<> localPositionRate;
RateCounter<> avatarBoundingBoxRate;
RateCounter<> avatarOrientationRate;
RateCounter<> avatarScaleRate;
RateCounter<> lookAtPositionRate;
RateCounter<> audioLoudnessRate;
RateCounter<> sensorToWorldRate;
RateCounter<> additionalFlagsRate;
RateCounter<> parentInfoRate;
RateCounter<> faceTrackerRate;
RateCounter<> jointDataRate;
};
class AvatarData : public QObject, public SpatiallyNestable { class AvatarData : public QObject, public SpatiallyNestable {
Q_OBJECT Q_OBJECT
@ -351,8 +368,11 @@ public:
SendAllData SendAllData
} AvatarDataDetail; } AvatarDataDetail;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail);
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector<JointData>* sentJointDataOut = nullptr); AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const;
virtual void doneEncoding(bool cullSmallChanges); virtual void doneEncoding(bool cullSmallChanges);
@ -380,7 +400,7 @@ public:
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
virtual void updateAttitude() {} // Tell skeleton mesh about changes virtual void updateAttitude() {} // Tell skeleton mesh about changes
glm::quat getHeadOrientation() { glm::quat getHeadOrientation() const {
lazyInitHeadData(); lazyInitHeadData();
return _headData->getOrientation(); return _headData->getOrientation();
} }
@ -419,7 +439,6 @@ public:
void setAudioAverageLoudness(float value) { _headData->setAudioAverageLoudness(value); } void setAudioAverageLoudness(float value) { _headData->setAudioAverageLoudness(value); }
// Scale // Scale
float getTargetScale() const;
virtual void setTargetScale(float targetScale); virtual void setTargetScale(float targetScale);
float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); }
@ -494,7 +513,7 @@ public:
// displayNameChanged returns true if displayName has changed, false otherwise. // displayNameChanged returns true if displayName has changed, false otherwise.
void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged); void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged);
QByteArray identityByteArray(); QByteArray identityByteArray() const;
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
const QString& getDisplayName() const { return _displayName; } const QString& getDisplayName() const { return _displayName; }
@ -534,8 +553,8 @@ public:
QJsonObject toJson() const; QJsonObject toJson() const;
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
glm::vec3 getClientGlobalPosition() { return _globalPosition; } glm::vec3 getClientGlobalPosition() const { return _globalPosition; }
glm::vec3 getGlobalBoundingBoxCorner() { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; }
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
@ -550,7 +569,7 @@ public:
Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const; Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const;
Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const; Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const;
int getJointCount() { return _jointData.size(); } int getJointCount() const { return _jointData.size(); }
QVector<JointData> getLastSentJointData() { QVector<JointData> getLastSentJointData() {
QReadLocker readLock(&_jointDataLock); QReadLocker readLock(&_jointDataLock);
@ -571,28 +590,27 @@ public slots:
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
float getTargetScale() { return _targetScale; } float getTargetScale() const { return _targetScale; } // why is this a slot?
void resetLastSent() { _lastToByteArray = 0; } void resetLastSent() { _lastToByteArray = 0; }
protected: protected:
void lazyInitHeadData(); void lazyInitHeadData() const;
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition); float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const;
float getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition); float getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) const;
bool avatarBoundingBoxChangedSince(quint64 time); bool avatarBoundingBoxChangedSince(quint64 time) const { return _avatarBoundingBoxChanged >= time; }
bool avatarScaleChangedSince(quint64 time); bool avatarScaleChangedSince(quint64 time) const { return _avatarScaleChanged >= time; }
bool lookAtPositionChangedSince(quint64 time); bool lookAtPositionChangedSince(quint64 time) const { return _headData->lookAtPositionChangedSince(time); }
bool audioLoudnessChangedSince(quint64 time); bool audioLoudnessChangedSince(quint64 time) const { return _headData->audioLoudnessChangedSince(time); }
bool sensorToWorldMatrixChangedSince(quint64 time); bool sensorToWorldMatrixChangedSince(quint64 time) const { return _sensorToWorldMatrixChanged >= time; }
bool additionalFlagsChangedSince(quint64 time); bool additionalFlagsChangedSince(quint64 time) const { return _additionalFlagsChanged >= time; }
bool parentInfoChangedSince(quint64 time) const { return _parentChanged >= time; }
bool faceTrackerInfoChangedSince(quint64 time) const { return true; } // FIXME
bool hasParent() { return !getParentID().isNull(); } bool hasParent() const { return !getParentID().isNull(); }
bool parentInfoChangedSince(quint64 time); bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
bool hasFaceTracker() { return _headData ? _headData->_isFaceTrackerConnected : false; }
bool faceTrackerInfoChangedSince(quint64 time);
glm::vec3 _handPosition; glm::vec3 _handPosition;
virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; } virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; }
@ -616,7 +634,7 @@ protected:
bool _forceFaceTrackerConnected; bool _forceFaceTrackerConnected;
bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar
HeadData* _headData { nullptr }; mutable HeadData* _headData { nullptr };
QUrl _skeletonModelURL; QUrl _skeletonModelURL;
bool _firstSkeletonCheck { true }; bool _firstSkeletonCheck { true };
@ -624,7 +642,7 @@ protected:
QVector<AttachmentData> _attachmentData; QVector<AttachmentData> _attachmentData;
QString _displayName; QString _displayName;
QString _sessionDisplayName { }; QString _sessionDisplayName { };
const QUrl& cannonicalSkeletonModelURL(const QUrl& empty); QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
float _displayNameTargetAlpha; float _displayNameTargetAlpha;
float _displayNameAlpha; float _displayNameAlpha;
@ -695,18 +713,7 @@ protected:
RateCounter<> _jointDataUpdateRate; RateCounter<> _jointDataUpdateRate;
// Some rate data for outgoing data // Some rate data for outgoing data
RateCounter<> _globalPositionRateOutbound; AvatarDataRate _outboundDataRate;
RateCounter<> _localPositionRateOutbound;
RateCounter<> _avatarBoundingBoxRateOutbound;
RateCounter<> _avatarOrientationRateOutbound;
RateCounter<> _avatarScaleRateOutbound;
RateCounter<> _lookAtPositionRateOutbound;
RateCounter<> _audioLoudnessRateOutbound;
RateCounter<> _sensorToWorldRateOutbound;
RateCounter<> _additionalFlagsRateOutbound;
RateCounter<> _parentInfoRateOutbound;
RateCounter<> _faceTrackerRateOutbound;
RateCounter<> _jointDataRateOutbound;
glm::vec3 _globalBoundingBoxDimensions; glm::vec3 _globalBoundingBoxDimensions;
glm::vec3 _globalBoundingBoxOffset; glm::vec3 _globalBoundingBoxOffset;

View file

@ -34,6 +34,7 @@
#include <tbb/concurrent_unordered_map.h> #include <tbb/concurrent_unordered_map.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <SharedUtil.h>
#include "DomainHandler.h" #include "DomainHandler.h"
#include "Node.h" #include "Node.h"
@ -182,15 +183,33 @@ public:
// This allows multiple threads (i.e. a thread pool) to share a lock // This allows multiple threads (i.e. a thread pool) to share a lock
// without deadlocking when a dying node attempts to acquire a write lock // without deadlocking when a dying node attempts to acquire a write lock
template<typename NestedNodeLambda> template<typename NestedNodeLambda>
void nestedEach(NestedNodeLambda functor) { void nestedEach(NestedNodeLambda functor,
QReadLocker readLock(&_nodeMutex); int* lockWaitOut = nullptr,
int* nodeTransformOut = nullptr,
int* functorOut = nullptr) {
auto start = usecTimestampNow();
{
QReadLocker readLock(&_nodeMutex);
auto endLock = usecTimestampNow();
if (lockWaitOut) {
*lockWaitOut = (endLock - start);
}
std::vector<SharedNodePointer> nodes(_nodeHash.size()); std::vector<SharedNodePointer> nodes(_nodeHash.size());
std::transform(_nodeHash.cbegin(), _nodeHash.cend(), nodes.begin(), [](const NodeHash::value_type& it) { std::transform(_nodeHash.cbegin(), _nodeHash.cend(), nodes.begin(), [](const NodeHash::value_type& it) {
return it.second; return it.second;
}); });
auto endTransform = usecTimestampNow();
if (nodeTransformOut) {
*nodeTransformOut = (endTransform - endLock);
}
functor(nodes.cbegin(), nodes.cend()); functor(nodes.cbegin(), nodes.cend());
auto endFunctor = usecTimestampNow();
if (functorOut) {
*functorOut = (endFunctor - endTransform);
}
}
} }
template<typename NodeLambda> template<typename NodeLambda>

View file

@ -179,9 +179,9 @@ public:
const glm::vec3& localVelocity, const glm::vec3& localVelocity,
const glm::vec3& localAngularVelocity); const glm::vec3& localAngularVelocity);
bool scaleChangedSince(quint64 time) { return _scaleChanged > time; } bool scaleChangedSince(quint64 time) const { return _scaleChanged > time; }
bool tranlationChangedSince(quint64 time) { return _translationChanged > time; } bool tranlationChangedSince(quint64 time) const { return _translationChanged > time; }
bool rotationChangedSince(quint64 time) { return _rotationChanged > time; } bool rotationChangedSince(quint64 time) const { return _rotationChanged > time; }
protected: protected:
const NestableType _nestableType; // EntityItem or an AvatarData const NestableType _nestableType; // EntityItem or an AvatarData

View file

@ -1,7 +1,6 @@
"use strict"; "use strict";
// newEditEntities.js // edit.js
// examples
// //
// Created by Brad Hefta-Gaub on 10/2/14. // Created by Brad Hefta-Gaub on 10/2/14.
// Persist toolbar by HRS 6/11/15. // Persist toolbar by HRS 6/11/15.
@ -13,7 +12,7 @@
// 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
// //
/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTook, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */
(function() { // BEGIN LOCAL_SCOPE (function() { // BEGIN LOCAL_SCOPE
@ -208,27 +207,7 @@ var toolBar = (function () {
var buttonHandlers = {}; // only used to tablet mode var buttonHandlers = {}; // only used to tablet mode
function addButton(name, image, handler) { function addButton(name, image, handler) {
if (Settings.getValue("HUDUIEnabled")) { buttonHandlers[name] = handler;
var imageUrl = TOOLS_PATH + image;
var button = toolBar.addButton({
objectName: name,
imageURL: imageUrl,
imageOffOut: 1,
imageOffIn: 2,
imageOnOut: 0,
imageOnIn: 2,
alpha: 0.9,
visible: true
});
if (handler) {
button.clicked.connect(function () {
Script.setTimeout(handler, 100);
});
}
return button;
} else {
buttonHandlers[name] = handler;
}
} }
var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_NONE = 0;
@ -272,7 +251,6 @@ var toolBar = (function () {
} }
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
print("fromQml: " + JSON.stringify(message));
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.popFromStack(); tablet.popFromStack();
switch (message.method) { switch (message.method) {
@ -299,42 +277,24 @@ var toolBar = (function () {
} }
}); });
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (Settings.getValue("HUDUIEnabled")) { activeButton = tablet.addButton({
systemToolbar = Toolbars.getToolbar(SYSTEM_TOOLBAR); icon: "icons/tablet-icons/edit-i.svg",
activeButton = systemToolbar.addButton({ activeIcon: "icons/tablet-icons/edit-a.svg",
objectName: EDIT_TOGGLE_BUTTON, text: "EDIT",
imageURL: TOOLS_PATH + "edit.svg", sortOrder: 10
visible: true, });
alpha: 0.9, tablet.screenChanged.connect(function (type, url) {
defaultState: 1 if (isActive && (type !== "QML" || url !== "Edit.qml")) {
}); that.toggle();
systemToolbar.fromQml.connect(fromQml); }
} else { });
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.fromQml.connect(fromQml);
activeButton = tablet.addButton({
icon: "icons/tablet-icons/edit-i.svg",
activeIcon: "icons/tablet-icons/edit-a.svg",
text: "EDIT",
sortOrder: 10
});
tablet.screenChanged.connect(function (type, url) {
if (isActive && (type !== "QML" || url !== "Edit.qml")) {
that.toggle();
}
});
tablet.fromQml.connect(fromQml);
}
activeButton.clicked.connect(function() { activeButton.clicked.connect(function() {
that.toggle(); that.toggle();
}); });
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar(EDIT_TOOLBAR);
toolBar.writeProperty("shown", false);
}
addButton("openAssetBrowserButton", "assets-01.svg", function(){ addButton("openAssetBrowserButton", "assets-01.svg", function(){
Window.showAssetServer(); Window.showAssetServer();
}); });
@ -348,34 +308,9 @@ var toolBar = (function () {
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
if (Settings.getValue("HUDUIEnabled")) { // tablet version of new-model dialog
// HUD-ui version of new-model dialog var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var result = Window.customPrompt({ tablet.pushOntoStack("NewModelDialog.qml");
textInput: {
label: "Model URL"
},
comboBox: {
label: "Automatic Collisions",
index: SHAPE_TYPE_DEFAULT,
items: SHAPE_TYPES
},
checkBox: {
label: "Dynamic",
checked: DYNAMIC_DEFAULT,
disableForItems: [
SHAPE_TYPE_STATIC_MESH
],
checkStateOnDisable: false,
warningOnDisable: "Models with automatic collisions set to 'Exact' cannot be dynamic"
}
});
handleNewModelDialogResult(result);
} else {
// tablet version of new-model dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack("NewModelDialog.qml");
}
}); });
addButton("newCubeButton", "cube-01.svg", function () { addButton("newCubeButton", "cube-01.svg", function () {
@ -497,11 +432,11 @@ var toolBar = (function () {
entityListTool.clearEntityList(); entityListTool.clearEntityList();
}; };
that.toggle = function () { that.toggle = function () {
that.setActive(!isActive); that.setActive(!isActive);
if (Settings.getValue("HUDUIEnabled")) { activeButton.editProperties({isActive: isActive});
activeButton.editProperties({isActive: isActive}); if (!isActive) {
tablet.gotoHomeScreen();
} }
}; };
@ -532,10 +467,8 @@ var toolBar = (function () {
cameraManager.disable(); cameraManager.disable();
selectionDisplay.triggerMapping.disable(); selectionDisplay.triggerMapping.disable();
} else { } else {
if (!Settings.getValue("HUDUIEnabled")) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.loadQMLSource("Edit.qml");
tablet.loadQMLSource("Edit.qml");
}
UserActivityLogger.enabledEdit(); UserActivityLogger.enabledEdit();
entityListTool.setVisible(true); entityListTool.setVisible(true);
gridTool.setVisible(true); gridTool.setVisible(true);
@ -546,15 +479,6 @@ var toolBar = (function () {
// everybody else to think that Interface has lost focus overall. fogbugzid:558 // everybody else to think that Interface has lost focus overall. fogbugzid:558
// Window.setFocus(); // Window.setFocus();
} }
if (Settings.getValue("HUDUIEnabled")) {
// Sets visibility of tool buttons, excluding the power button
toolBar.writeProperty("shown", active);
var visible = toolBar.readProperty("visible");
if (active && !visible) {
toolBar.writeProperty("shown", false);
toolBar.writeProperty("shown", true);
}
}
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
}; };
@ -815,6 +739,12 @@ function mouseClickEvent(event) {
orientation = MyAvatar.orientation; orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
if (event.isShifted) {
particleExplorerTool.destroyWebView();
}
if (properties.type !== "ParticleEffect") {
particleExplorerTool.destroyWebView();
}
if (!event.isShifted) { if (!event.isShifted) {
selectionManager.setSelections([foundEntity]); selectionManager.setSelections([foundEntity]);
@ -1493,16 +1423,8 @@ var PropertiesTool = function (opts) {
var that = {}; var that = {};
var webView = null; var webView = null;
if (Settings.getValue("HUDUIEnabled")) { webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView = new OverlayWebWindow({ webView.setVisible = function(value) {};
title: 'Entity Properties',
source: ENTITY_PROPERTIES_URL,
toolWindow: true
});
} else {
webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView.setVisible = function(value) {};
}
var visible = false; var visible = false;
@ -1924,18 +1846,7 @@ function selectParticleEntity(entityID) {
selectedParticleEntity = entityID; selectedParticleEntity = entityID;
particleExplorerTool.setActiveParticleEntity(entityID); particleExplorerTool.setActiveParticleEntity(entityID);
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
if (Settings.getValue("HUDUIEnabled")) {
particleExplorerTool.webView.webEventReceived.connect(function (data) {
data = JSON.parse(data);
if (data.messageType === "page_loaded") {
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
}
});
} else {
// in the tablet version, the page was loaded earlier
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
}
} }
entityListTool.webView.webEventReceived.connect(function (data) { entityListTool.webView.webEventReceived.connect(function (data) {
@ -1949,7 +1860,7 @@ entityListTool.webView.webEventReceived.connect(function (data) {
return; return;
} }
// Destroy the old particles web view first // Destroy the old particles web view first
selectParticleEntity(ids[0]) selectParticleEntity(ids[0]);
} else { } else {
selectedParticleEntity = 0; selectedParticleEntity = 0;
particleExplorerTool.destroyWebView(); particleExplorerTool.destroyWebView();

View file

@ -871,6 +871,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer {
float: right; float: right;
margin-right: 0; margin-right: 0;
background-color: #ff0000; background-color: #ff0000;
min-width: 90px;
} }
#entity-list { #entity-list {

View file

@ -1,18 +1,22 @@
var ENTITY_LIST_HTML_URL = Script.resolvePath('../html/entityList.html'); "use strict";
// entityList.js
//
// Copyright 2014 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 EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages,
cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */
EntityListTool = function(opts) { EntityListTool = function(opts) {
var that = {}; var that = {};
var webView = null; var webView = null;
if (Settings.getValue("HUDUIEnabled")) { webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var url = ENTITY_LIST_HTML_URL; webView.setVisible = function(value) {};
webView = new OverlayWebWindow({
title: 'Entity List', source: url, toolWindow: true
});
} else {
webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView.setVisible = function(value) {};
}
var filterInView = false; var filterInView = false;
var searchRadius = 100; var searchRadius = 100;
@ -30,7 +34,7 @@ EntityListTool = function(opts) {
that.toggleVisible = function() { that.toggleVisible = function() {
that.setVisible(!visible); that.setVisible(!visible);
} };
selectionManager.addEventListener(function() { selectionManager.addEventListener(function() {
var selectedIDs = []; var selectedIDs = [];
@ -49,7 +53,7 @@ EntityListTool = function(opts) {
that.clearEntityList = function () { that.clearEntityList = function () {
var data = { var data = {
type: 'clearEntityList' type: 'clearEntityList'
} };
webView.emitScriptEvent(JSON.stringify(data)); webView.emitScriptEvent(JSON.stringify(data));
}; };
@ -91,8 +95,8 @@ EntityListTool = function(opts) {
} }
var selectedIDs = []; var selectedIDs = [];
for (var i = 0; i < selectionManager.selections.length; i++) { for (var j = 0; j < selectionManager.selections.length; j++) {
selectedIDs.push(selectionManager.selections[i].id); selectedIDs.push(selectionManager.selections[j].id);
} }
var data = { var data = {
@ -101,7 +105,7 @@ EntityListTool = function(opts) {
selectedIDs: selectedIDs, selectedIDs: selectedIDs,
}; };
webView.emitScriptEvent(JSON.stringify(data)); webView.emitScriptEvent(JSON.stringify(data));
} };
webView.webEventReceived.connect(function(data) { webView.webEventReceived.connect(function(data) {
data = JSON.parse(data); data = JSON.parse(data);

View file

@ -229,15 +229,8 @@ GridTool = function(opts) {
var listeners = []; var listeners = [];
var webView = null; var webView = null;
if (Settings.getValue("HUDUIEnabled")) { webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var url = GRID_CONTROLS_HTML_URL; webView.setVisible = function(value) {};
webView = new OverlayWebWindow({
title: 'Grid', source: url, toolWindow: true
});
} else {
webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView.setVisible = function(value) {};
}
horizontalGrid.addListener(function(data) { horizontalGrid.addListener(function(data) {
webView.emitScriptEvent(JSON.stringify(data)); webView.emitScriptEvent(JSON.stringify(data));

View file

@ -32,6 +32,8 @@ var gui = null;
var settings = new Settings(); var settings = new Settings();
var updateInterval; var updateInterval;
var active = false;
var currentInputField; var currentInputField;
var storedController; var storedController;
//CHANGE TO WHITELIST //CHANGE TO WHITELIST
@ -363,6 +365,19 @@ function listenForSettingsUpdates() {
} else { } else {
loadGUI(); loadGUI();
} }
if (!active) {
// gui.toggleHide();
gui.closed = false;
}
active = true;
} else if (data.messageType === "particle_close") {
// none of this seems to work.
// if (active) {
// gui.toggleHide();
// }
active = false;
gui.closed = true;
} }
}); });
} }

View file

@ -18,33 +18,21 @@ ParticleExplorerTool = function() {
var that = {}; var that = {};
that.createWebView = function() { that.createWebView = function() {
if (Settings.getValue("HUDUIEnabled")) { that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var url = PARTICLE_EXPLORER_HTML_URL; that.webView.setVisible = function(value) {};
that.webView = new OverlayWebWindow({
title: 'Particle Explorer',
source: url,
toolWindow: true
});
} else {
that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
that.webView.setVisible = function(value) {};
}
that.webView.setVisible(true);
that.webView.webEventReceived.connect(that.webEventReceived); that.webView.webEventReceived.connect(that.webEventReceived);
} }
that.destroyWebView = function() { that.destroyWebView = function() {
if (!that.webView) { if (!that.webView) {
return; return;
} }
if (Settings.getValue("HUDUIEnabled")) {
that.webView.close();
that.webView = null;
}
that.activeParticleEntity = 0; that.activeParticleEntity = 0;
var messageData = {
messageType: "particle_close"
};
that.webView.emitScriptEvent(JSON.stringify(messageData));
} }
that.webEventReceived = function(data) { that.webEventReceived = function(data) {
@ -58,9 +46,5 @@ ParticleExplorerTool = function() {
that.activeParticleEntity = id; that.activeParticleEntity = id;
} }
if (Settings.getValue("HUDUIEnabled")) {
that.createWebView();
}
return that; return that;
}; };

View file

@ -49,7 +49,6 @@
} }
function onWebEventReceived(event) { function onWebEventReceived(event) {
print("Script received a web event, its type is " + typeof event);
if (typeof event === "string") { if (typeof event === "string") {
event = JSON.parse(event); event = JSON.parse(event);
} }