Merge branch 'master' into bug-fix/entity-mat-avatar-dual-quaternions

This commit is contained in:
Anthony J. Thibault 2018-02-22 08:44:21 -08:00
commit 86351f19a9
268 changed files with 12948 additions and 5933 deletions

View file

@ -1,6 +1,6 @@
set(TARGET_NAME native-lib)
setup_hifi_library()
link_hifi_libraries(shared networking gl gpu image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
target_opengl()
target_bullet()

View file

@ -464,32 +464,41 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> me
auto replyPacket = NLPacketList::create(PacketType::AssetMappingOperationReply, QByteArray(), true, true);
replyPacket->writePrimitive(messageID);
bool canWriteToAssetServer = true;
if (senderNode) {
canWriteToAssetServer = senderNode->getCanWriteToAssetServer();
}
switch (operationType) {
case AssetMappingOperationType::Get:
handleGetMappingOperation(*message, senderNode, *replyPacket);
handleGetMappingOperation(*message, *replyPacket);
break;
case AssetMappingOperationType::GetAll:
handleGetAllMappingOperation(*message, senderNode, *replyPacket);
handleGetAllMappingOperation(*replyPacket);
break;
case AssetMappingOperationType::Set:
handleSetMappingOperation(*message, senderNode, *replyPacket);
handleSetMappingOperation(*message, canWriteToAssetServer, *replyPacket);
break;
case AssetMappingOperationType::Delete:
handleDeleteMappingsOperation(*message, senderNode, *replyPacket);
handleDeleteMappingsOperation(*message, canWriteToAssetServer, *replyPacket);
break;
case AssetMappingOperationType::Rename:
handleRenameMappingOperation(*message, senderNode, *replyPacket);
handleRenameMappingOperation(*message, canWriteToAssetServer, *replyPacket);
break;
case AssetMappingOperationType::SetBakingEnabled:
handleSetBakingEnabledOperation(*message, senderNode, *replyPacket);
handleSetBakingEnabledOperation(*message, canWriteToAssetServer, *replyPacket);
break;
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacketList(std::move(replyPacket), *senderNode);
if (senderNode) {
nodeList->sendPacketList(std::move(replyPacket), *senderNode);
} else {
nodeList->sendPacketList(std::move(replyPacket), message->getSenderSockAddr());
}
}
void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket) {
QString assetPath = message.readString();
QUrl url { assetPath };
@ -568,7 +577,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
}
}
void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
void AssetServer::handleGetAllMappingOperation(NLPacketList& replyPacket) {
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
uint32_t count = (uint32_t)_fileMappings.size();
@ -591,8 +600,8 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN
}
}
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
if (senderNode->getCanWriteToAssetServer()) {
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
if (hasWriteAccess) {
QString assetPath = message.readString();
auto assetHash = message.read(AssetUtils::SHA256_HASH_LENGTH).toHex();
@ -614,8 +623,8 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode
}
}
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
if (senderNode->getCanWriteToAssetServer()) {
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
if (hasWriteAccess) {
int numberOfDeletedMappings { 0 };
message.readPrimitive(&numberOfDeletedMappings);
@ -642,8 +651,8 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
}
}
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
if (senderNode->getCanWriteToAssetServer()) {
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
if (hasWriteAccess) {
QString oldPath = message.readString();
QString newPath = message.readString();
@ -664,8 +673,8 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN
}
}
void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
if (senderNode->getCanWriteToAssetServer()) {
void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
if (hasWriteAccess) {
bool enabled { true };
message.readPrimitive(&enabled);
@ -739,9 +748,14 @@ void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, Shared
}
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
bool canWriteToAssetServer = true;
if (senderNode) {
canWriteToAssetServer = senderNode->getCanWriteToAssetServer();
}
if (senderNode->getCanWriteToAssetServer()) {
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
if (canWriteToAssetServer) {
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(message->getSourceID());
auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit);
_transferTaskPool.start(task);
@ -761,7 +775,11 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
// send off the packet
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode);
if (senderNode) {
nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode);
} else {
nodeList->sendPacket(std::move(permissionErrorPacket), message->getSenderSockAddr());
}
}
}

View file

@ -21,17 +21,9 @@
#include "AssetUtils.h"
#include "ReceivedMessage.h"
namespace std {
template <>
struct hash<QString> {
size_t operator()(const QString& v) const { return qHash(v); }
};
}
#include "RegisteredMetaTypes.h"
struct AssetMeta {
AssetMeta() {
}
int bakeVersion { 0 };
bool failedLastBake { false };
QString lastBakeErrors;
@ -60,14 +52,15 @@ private slots:
void sendStatsPacket() override;
private:
using Mappings = std::unordered_map<QString, QString>;
void handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket);
void handleGetAllMappingOperation(NLPacketList& replyPacket);
void handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
void handleDeleteMappingsOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
void handleRenameMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
void handleSetBakingEnabledOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
void handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleAssetServerBackup(ReceivedMessage& message, NLPacketList& replyPacket);
void handleAssetServerRestore(ReceivedMessage& message, NLPacketList& replyPacket);
// Mapping file operations must be called from main assignment thread only
bool loadMappingsFromFile();
@ -111,7 +104,7 @@ private:
/// Remove baked paths when the original asset is deleteds
void removeBakedPathsForDeletedAsset(AssetUtils::AssetHash originalAssetHash);
Mappings _fileMappings;
AssetUtils::Mappings _fileMappings;
QDir _resourcesDirectory;
QDir _filesDirectory;

View file

@ -112,5 +112,9 @@ void SendAssetTask::run() {
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacketList(std::move(replyPacketList), *_senderNode);
if (_senderNode) {
nodeList->sendPacketList(std::move(replyPacketList), *_senderNode);
} else {
nodeList->sendPacketList(std::move(replyPacketList), _message->getSenderSockAddr());
}
}

View file

@ -41,9 +41,12 @@ void UploadAssetTask::run() {
uint64_t fileSize;
buffer.read(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from"
<< uuidStringWithoutCurlyBraces(_senderNode->getUUID());
if (_senderNode) {
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID());
} else {
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << _receivedMessage->getSenderSockAddr();
}
auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true);
replyPacket->writePrimitive(messageID);
@ -55,9 +58,12 @@ void UploadAssetTask::run() {
auto hash = AssetUtils::hashData(fileData);
auto hexHash = hash.toHex();
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID())
<< "is: (" << hexHash << ") ";
if (_senderNode) {
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()) << "is: (" << hexHash << ")";
} else {
qDebug() << "Hash for uploaded file from" << _receivedMessage->getSenderSockAddr() << "is: (" << hexHash << ")";
}
QFile file { _resourcesDir.filePath(QString(hexHash)) };
@ -103,5 +109,9 @@ void UploadAssetTask::run() {
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(replyPacket), *_senderNode);
if (_senderNode) {
nodeList->sendPacket(std::move(replyPacket), *_senderNode);
} else {
nodeList->sendPacket(std::move(replyPacket), _receivedMessage->getSenderSockAddr());
}
}

View file

@ -22,6 +22,8 @@ setup_memory_debugger()
symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources")
# link the shared hifi libraries
include_hifi_library_headers(gpu)
include_hifi_library_headers(graphics)
link_hifi_libraries(embedded-webserver networking shared avatars)
# find OpenSSL

View file

@ -1,12 +1,6 @@
{
"version": 2.1,
"settings": [
{
"name": "label",
"label": "Label",
"settings": [
]
},
{
"name": "metaverse",
"label": "Metaverse / Networking",
@ -15,7 +9,8 @@
"name": "access_token",
"label": "Access Token",
"help": "This is your OAuth access token to connect this domain-server with your High Fidelity account. <br/>It can be generated by clicking the 'Connect Account' button above.<br/>You can also go to the <a href='https://metaverse.highfidelity.com/user/security' target='_blank'>My Security</a> page of your account and generate a token with the 'domains' scope and paste it here.",
"advanced": true
"advanced": true,
"backup": false
},
{
"name": "id",
@ -55,8 +50,8 @@
]
},
{
"label": "Places / Paths",
"html_id": "places_paths",
"label": "Paths",
"html_id": "paths",
"restart": false,
"settings": [
{
@ -64,6 +59,7 @@
"label": "Paths",
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
"type": "table",
"content_setting": true,
"can_add_new_rows": true,
"key": {
"name": "path",
@ -164,7 +160,8 @@
{
"name": "http_username",
"label": "HTTP Username",
"help": "Username used for basic HTTP authentication."
"help": "Username used for basic HTTP authentication.",
"backup": false
},
{
"name": "http_password",
@ -172,7 +169,8 @@
"type": "password",
"help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.",
"password_placeholder": "******",
"value-hidden": true
"value-hidden": true,
"backup": false
},
{
"name": "verify_http_password",
@ -935,6 +933,7 @@
"name": "persistent_scripts",
"type": "table",
"label": "Persistent Scripts",
"content_setting": true,
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
"can_add_new_rows": true,
"columns": [
@ -955,99 +954,6 @@
}
]
},
{
"name": "asset_server",
"label": "Asset Server (ATP)",
"assignment-types": [ 3 ],
"settings": [
{
"name": "enabled",
"type": "checkbox",
"label": "Enabled",
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
"default": true,
"advanced": true
},
{
"name": "assets_path",
"type": "string",
"label": "Assets Path",
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
"default": "",
"advanced": true
},
{
"name": "assets_filesize_limit",
"type": "int",
"label": "File Size Limit",
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
"default": 0,
"advanced": true
}
]
},
{
"name": "entity_script_server",
"label": "Entity Script Server (ESS)",
"assignment-types": [ 5 ],
"settings": [
{
"name": "entity_pps_per_script",
"label": "Entity PPS per script",
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
"default": 900,
"type": "int",
"advanced": true
},
{
"name": "max_total_entity_pps",
"label": "Maximum Total Entity PPS",
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
"default": 9000,
"type": "int",
"advanced": true
}
]
},
{
"name": "avatars",
"label": "Avatars",
"assignment-types": [ 1, 2 ],
"settings": [
{
"name": "min_avatar_height",
"type": "double",
"label": "Minimum Avatar Height (meters)",
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
"placeholder": 0.4,
"default": 0.4
},
{
"name": "max_avatar_height",
"type": "double",
"label": "Maximum Avatar Height (meters)",
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
"placeholder": 5.2,
"default": 5.2
},
{
"name": "avatar_whitelist",
"label": "Avatars Allowed from:",
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
"placeholder": "",
"default": "",
"advanced": true
},
{
"name": "replacement_avatar",
"label": "Replacement Avatar for disallowed avatars",
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
"placeholder": "",
"default": "",
"advanced": true
}
]
},
{
"name": "audio_threading",
"label": "Audio Threading",
@ -1080,6 +986,7 @@
"name": "attenuation_per_doubling_in_distance",
"label": "Default Domain Attenuation",
"help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)",
"content_setting": true,
"placeholder": "0.5",
"default": "0.5",
"advanced": false
@ -1105,6 +1012,7 @@
"label": "Zones",
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
"numbered": false,
"content_setting": true,
"can_add_new_rows": true,
"key": {
"name": "name",
@ -1155,6 +1063,7 @@
"type": "table",
"label": "Attenuation Coefficients",
"help": "In this table you can set custom attenuation coefficients between audio zones",
"content_setting": true,
"numbered": true,
"can_order": true,
"can_add_new_rows": true,
@ -1185,6 +1094,7 @@
"label": "Reverb Settings",
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
"numbered": true,
"content_setting": true,
"can_add_new_rows": true,
"columns": [
{
@ -1266,9 +1176,82 @@
}
]
},
{
"name": "avatars",
"label": "Avatars",
"assignment-types": [ 1, 2 ],
"settings": [
{
"name": "min_avatar_height",
"type": "double",
"label": "Minimum Avatar Height (meters)",
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
"placeholder": 0.4,
"default": 0.4
},
{
"name": "max_avatar_height",
"type": "double",
"label": "Maximum Avatar Height (meters)",
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
"placeholder": 5.2,
"default": 5.2
},
{
"name": "avatar_whitelist",
"label": "Avatars Allowed from:",
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
"placeholder": "",
"default": "",
"advanced": true
},
{
"name": "replacement_avatar",
"label": "Replacement Avatar for disallowed avatars",
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
"placeholder": "",
"default": "",
"advanced": true
}
]
},
{
"name": "avatar_mixer",
"label": "Avatar Mixer",
"assignment-types": [
1
],
"settings": [
{
"name": "max_node_send_bandwidth",
"type": "double",
"label": "Per-Node Bandwidth",
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
"placeholder": 5.0,
"default": 5.0,
"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
}
]
},
{
"name": "entity_server_settings",
"label": "Entity Server Settings",
"label": "Entities",
"assignment-types": [
6
],
@ -1309,6 +1292,7 @@
"name": "entityEditFilter",
"label": "Filter Entity Edits",
"help": "Check all entity edits against this filter function.",
"content_setting": true,
"placeholder": "url whose content is like: function filter(properties) { return properties; }",
"default": "",
"advanced": true
@ -1503,35 +1487,55 @@
]
},
{
"name": "avatar_mixer",
"label": "Avatar Mixer",
"assignment-types": [
1
],
"name": "asset_server",
"label": "Asset Server (ATP)",
"assignment-types": [ 3 ],
"settings": [
{
"name": "max_node_send_bandwidth",
"type": "double",
"label": "Per-Node Bandwidth",
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
"placeholder": 5.0,
"default": 5.0,
"advanced": true
},
{
"name": "auto_threads",
"label": "Automatically determine thread count",
"name": "enabled",
"type": "checkbox",
"help": "Allow system to determine number of threads (recommended)",
"default": false,
"label": "Enabled",
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
"default": true,
"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",
"name": "assets_path",
"type": "string",
"label": "Assets Path",
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
"default": "",
"advanced": true
},
{
"name": "assets_filesize_limit",
"type": "int",
"label": "File Size Limit",
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
"default": 0,
"advanced": true
}
]
},
{
"name": "entity_script_server",
"label": "Entity Script Server (ESS)",
"assignment-types": [ 5 ],
"settings": [
{
"name": "entity_pps_per_script",
"label": "Entity PPS per script",
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
"default": 900,
"type": "int",
"advanced": true
},
{
"name": "max_total_entity_pps",
"label": "Maximum Total Entity PPS",
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
"default": 9000,
"type": "int",
"advanced": true
}
]

View file

@ -0,0 +1,7 @@
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/form2js.min.js'></script>
<script src='/js/bootstrap-switch.min.js'></script>
<script src='/js/shared.js'></script>
<script src='/js/base-settings.js'></script>

View file

@ -0,0 +1,59 @@
<div class="col-md-10 col-md-offset-1 col-xs-12">
<div class="row">
<div class="col-xs-12">
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span class="alert-link">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
</span>
</div>
<form id="settings-form" role="form">
<script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% if (!group.hidden) { %>
<% var settings = _.partition(group.settings, function(value, index) { %>
<% return !value.deprecated %>
<% })[0] %>
<% var split_settings = _.partition(group.settings, function(value, index) { %>
<% return !value.advanced %>
<% }) %>
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.html_id %>
<div id="<%- panelID %>_group" class="anchor"></div>
<div class="panel panel-default<%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
<span class="badge"></span>
</div>
<div class="panel-body">
<% _.each(split_settings[0], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false) %>
<% }); %>
<% if (split_settings[1].length > 0) { %>
<button type="button" class="btn btn-default" data-toggle="collapse" data-target="#<%- panelID %>-advanced">Advanced Settings <span class="caret"></span></button>
<div id="<%- panelID %>-advanced" class="collapse advanced-settings-section">
<% _.each(split_settings[1], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true) %>
<% }); %>
</div>
<% } %>
</div>
</div>
<% } %>
<% }); %>
</script>
<div id="panels"></div>
</form>
</div>
</div>
</div>

View file

@ -1,45 +1,19 @@
<!--#include virtual="header.html"-->
<div class="col-md-10 col-md-offset-1">
<div class="row">
<div class="col-xs-12">
<div class="alert" style="display:none;"></div>
</div>
</div>
<script type="text/javascript">
var Settings = {
content_settings: true,
endpoint: "/content-settings.json",
path: "/content/"
};
</script>
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Upload Entities File</h3>
</div>
<form id="upload-form" action="upload" enctype="multipart/form-data" method="post">
<div class="panel-body">
<p>
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
</p>
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
<label class="control-label">Windows</label>
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
<label class="control-label">OSX</label>
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
<label class="control-label">Linux</label>
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
<br>
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
<br>
</div>
<div class="panel-footer">
<input type="submit" class="btn btn-info" value="Upload">
</div>
</form>
</div>
</div>
</div>
</div>
<!--#include virtual="base-settings.html"-->
<!--#include virtual="footer.html"-->
<script src='js/content.js'></script>
<script src='/js/sweetalert.min.js'></script>
<!--#include virtual="base-settings-scripts.html"-->
<script src="js/content.js"></script>
<!--#include virtual="page-end.html"-->

View file

@ -1,14 +1,6 @@
$(document).ready(function(){
function showSpinnerAlert(title) {
swal({
title: title,
text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>',
html: true,
showConfirmButton: false,
allowEscapeKey: false
});
}
Settings.afterReloadActions = function() {};
var frm = $('#upload-form');
frm.submit(function (ev) {

View file

@ -18,6 +18,14 @@ body {
margin-top: 70px;
}
/* unfortunate hack so that anchors go to the right place with fixed navbar */
:target:before {
content: " ";
display: block;
height: 70px;
margin-top: -70px;
}
[hidden] {
display: none !important;
}
@ -118,11 +126,6 @@ span.port {
margin-top: 10px;
}
#small-save-button {
width: 100%;
margin-bottom: 15px;
}
td.buttons {
width: 30px;
}
@ -345,3 +348,89 @@ table .headers + .headers td {
text-align: center;
margin-top: 20px;
}
@media (min-width: 768px) {
ul.nav li.dropdown-on-hover:hover ul.dropdown-menu {
display: block;
}
}
ul.nav li.dropdown ul.dropdown-menu {
padding: 0px 0px;
}
ul.nav li.dropdown li a {
padding-top: 7px;
padding-bottom: 7px;
}
ul.nav li.dropdown li a:hover {
color: white;
background-color: #337ab7;
}
ul.nav li.dropdown ul.dropdown-menu .divider {
margin: 0px 0;
}
#visit-domain-link {
background-color: transparent;
}
.navbar-btn {
margin-left: 10px;
}
#save-settings-xs-button {
float: right;
margin-right: 10px;
}
#button-bars {
display: inline-block;
float: left;
}
#hamburger-badge {
position: relative;
top: -2px;
float: left;
margin-right: 10px;
margin-left: 0px;
}
#restart-server {
margin-left: 0px;
}
#restart-server:hover {
text-decoration: none;
}
.badge {
margin-left: 5px;
background-color: #00B4EF !important;
}
.panel-title {
display: inline-block;
}
#visit-hmd-icon {
width: 25px;
position: relative;
top: -1px;
}
.advanced-settings-section {
margin-top: 20px;
}
#restore-settings-button {
margin-top: 10px;
}
/* fix for https://bugs.webkit.org/show_bug.cgi?id=39620 */
.save-button-text {
pointer-events: none;
}

View file

@ -17,30 +17,47 @@
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#collapsed-navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span id="hamburger-badge" class="badge"></span>
<div id="button-bars">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</div>
</button>
<button id="save-settings-xs-button" class="save-button btn btn-success navbar-btn hidden-sm hidden-md hidden-lg" disabled="true"><span class="save-button-text">Save</span></button>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<div class="collapse navbar-collapse" id="collapsed-navbar">
<ul class="nav navbar-nav">
<li><a href="/">Nodes</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/assignment">New Assignment</a></li>
</ul>
<li><a href="/assignment">Assignment</a></li>
<li class="dropdown dropdown-on-hover">
<a href="/content/" class="hidden-xs">Content <span class="content-settings-badge badge"></span> <span class="caret"></span></a>
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Content <span class="content-badge badge"></span> <span class="caret"></span></a>
<ul id="content-settings-nav-dropdown" class="dropdown-menu" role="menu">
</ul>
</li>
<li class="dropdown dropdown-on-hover">
<a href="/settings/" class="hidden-xs">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
<ul id="domain-settings-nav-dropdown" class="dropdown-menu" role="menu">
</ul>
</li>
<li><a href="/content/">Content</a></li>
<li><a href="/settings/">Settings</a></li>
</ul>
<ul class="nav navbar-right navbar-nav">
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
<a id="visit-domain-link" class="btn btn-default navbar-btn" role="button" target="_blank" style="display: none;">
<img id="visit-hmd-icon" src="/images/hmd-w-eyes.svg" alt="Head-mounted display" />
Visit in VR
</a>
<button id="save-settings-button" class="save-button btn btn-success navbar-btn hidden-xs" disabled="true"><span class="save-button-text">Save</span></button>
<a href="#" id="restart-server" class="navbar-btn btn btn-link"><span class="glyphicon glyphicon-refresh"></span> Restart</a>
</ul>
</div>
</div><!-- /.container-fluid -->
@ -50,7 +67,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">domain-server is restarting</h4>
<h4 class="modal-title">Domain Server is restarting</h4>
</div>
<div class="modal-body">
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, 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 456 244" style="enable-background:new 0 0 456 244;" xml:space="preserve">
<style type="text/css">
.st0{fill:#666666;}
</style>
<path class="st0" d="M352.8,3.4h-250C49.6,3.4,6.3,46.2,6.3,98.9v44.9c0,52.7,43.3,95.5,96.5,95.5h67.6c19.4,0,31.9-17.9,42.9-33.6
c4.2-6.1,11.1-15.9,14.8-17.9c3.6,2.1,10.4,12.2,14.5,18.4c10.4,15.5,22.1,33.1,40.3,33.1h69.9c53.3,0,96.5-42.9,96.5-95.5V98.9
C449.3,46.2,406,3.4,352.8,3.4z M129.6,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
S151.9,157.6,129.6,157.6z M328.4,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
S350.8,157.6,328.4,157.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 928 B

File diff suppressed because it is too large Load diff

View file

@ -23,16 +23,22 @@ function showRestartModal() {
}, 1000);
}
function settingsGroupAnchor(base, html_id) {
return base + "#" + html_id + "_group"
}
$(document).ready(function(){
var url = window.location;
// Will only work if string in href matches with location
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
// Will also work for relative and absolute hrefs
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e) {
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e) {
swal( {
title: "Are you sure?",
text: "This will restart your domain server, causing your domain to be briefly offline.",
@ -45,4 +51,47 @@ $(document).ready(function(){
});
return false;
});
var $contentDropdown = $('#content-settings-nav-dropdown');
var $settingsDropdown = $('#domain-settings-nav-dropdown');
// for pages that have the settings dropdowns
if ($contentDropdown.length && $settingsDropdown.length) {
// make a JSON request to get the dropdown menus for content and settings
// we don't error handle here because the top level menu is still clickable and usables if this fails
$.getJSON('/settings-menu-groups.json', function(data){
function makeGroupDropdownElement(group, base) {
var html_id = group.html_id ? group.html_id : group.name;
return "<li class='setting-group'><a href='" + settingsGroupAnchor(base, html_id) + "'>" + group.label + "<span class='badge'></span></a></li>";
}
$.each(data.content_settings, function(index, group){
if (index > 0) {
$contentDropdown.append("<li role='separator' class='divider'></li>");
}
$contentDropdown.append(makeGroupDropdownElement(group, "/content/"));
});
$.each(data.domain_settings, function(index, group){
if (index > 0) {
$settingsDropdown.append("<li role='separator' class='divider'></li>");
}
$settingsDropdown.append(makeGroupDropdownElement(group, "/settings/"));
// for domain settings, we add a dummy "Places" group that we fill
// via the API - add it to the dropdown menu in the right spot
// which is after "Metaverse / Networking"
if (group.name == "metaverse") {
$settingsDropdown.append("<li role='separator' class='divider'></li>");
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/"));
}
});
// append a link for the "Settings Backup" panel
$settingsDropdown.append("<li role='separator' class='divider'></li>");
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'settings_backup', label: 'Settings Backup'}, "/settings"));
});
}
});

View file

@ -1,6 +1,8 @@
var Settings = {
showAdvanced: false,
ADVANCED_CLASS: 'advanced-setting',
if (typeof Settings === "undefined") {
Settings = {};
}
Object.assign(Settings, {
DEPRECATED_CLASS: 'deprecated-setting',
TRIGGER_CHANGE_CLASS: 'trigger-change',
DATA_ROW_CLASS: 'value-row',
@ -41,7 +43,7 @@ var Settings = {
FORM_ID: 'settings-form',
INVALID_ROW_CLASS: 'invalid-input',
DATA_ROW_INDEX: 'data-row-index'
};
});
var URLs = {
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
@ -95,7 +97,13 @@ var DOMAIN_ID_TYPE_FULL = 2;
var DOMAIN_ID_TYPE_UNKNOWN = 3;
function domainIDIsSet() {
return Settings.data.values.metaverse.id.length > 0;
if (typeof Settings.data.values.metaverse !== 'undefined' &&
typeof Settings.data.values.metaverse.id !== 'undefined') {
return Settings.data.values.metaverse.id.length > 0;
} else {
return false;
}
}
function getCurrentDomainIDType() {
@ -156,11 +164,12 @@ function getDomainFromAPI(callback) {
if (callback === undefined) {
callback = function() {};
}
var domainID = Settings.data.values.metaverse.id;
if (domainID === null || domainID === undefined || domainID === '') {
if (!domainIDIsSet()) {
callback({ status: 'fail' });
return null;
} else {
var domainID = Settings.data.values.metaverse.id;
}
pendingDomainRequest = $.ajax({

File diff suppressed because one or more lines are too long

View file

@ -1,104 +1,29 @@
<!--#include virtual="header.html"-->
<div class="col-md-10 col-md-offset-1">
<div class="row">
<div class="col-md-12">
<div class="alert" style="display:none;"></div>
</div>
</div>
<script type="text/javascript">
var Settings = {
content_settings: false,
endpoint: "/settings.json",
path: "/settings/"
};
</script>
<div class="row">
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col">
<script id="list-group-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% if (!group.hidden) { %>
<% panelID = group.name ? group.name : group.html_id %>
<li>
<a href="#<%- panelID %>" class="list-group-item">
<span class="badge"></span>
<%- group.label %>
</a>
</li>
<% } %>
<% }); %>
</script>
<ul class="nav nav-pills nav-stacked">
</ul>
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
<button class="btn btn-success save-button" disabled>Save</button>
<div id="manage-cloud-domains-link" style="display: none;">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
</div>
</div>
</div>
<div class="col-md-9 col-sm-9 col-xs-12">
<div class="col-md-10 col-md-offset-1 col-xs-12">
<div class="row">
<div class="col-xs-12">
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span class="alert-link">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
</span>
</div>
<form id="settings-form" role="form">
<script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% if (!group.hidden) { %>
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
<% if (isAdvanced) { %>
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
<% } %>
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.html_id %>
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% _.each(split_settings[0], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false) %>
<% }); %>
<% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true) %>
<% }); %>
<% }%>
</div>
</div>
<% } %>
<% }); %>
</script>
<div id="panels"></div>
</form>
</div>
</div>
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
<button class="btn btn-success save-button" id="small-save-button">Save</button>
</div>
<div class="alert alert-info">Your domain content settings are now available in <a href='/content/'>Content</a></div>
</div>
</div>
</div>
<!--#include virtual="base-settings.html"-->
<!--#include virtual="footer.html"-->
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/sha256.js'></script>
<script src='js/form2js.min.js'></script>
<script src='js/bootstrap-switch.min.js'></script>
<script src='/js/shared.js'></script>
<script src='js/settings.js'></script>
<!--#include virtual="base-settings-scripts.html"-->
<script src="js/settings.js"></script>
<!--#include virtual="page-end.html"-->

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,400 @@
//
// BackupSupervisor.cpp
// domain-server/src
//
// Created by Clement Brisset on 1/12/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "BackupSupervisor.h"
#include <QJsonDocument>
#include <QDate>
#include <AssetClient.h>
#include <AssetRequest.h>
#include <AssetUpload.h>
#include <MappingRequest.h>
#include <PathUtils.h>
const QString BACKUPS_DIR = "backups/";
const QString ASSETS_DIR = "files/";
const QString MAPPINGS_PREFIX = "mappings-";
using namespace std;
BackupSupervisor::BackupSupervisor() {
_backupsDirectory = PathUtils::getAppDataPath() + BACKUPS_DIR;
QDir backupDir { _backupsDirectory };
if (!backupDir.exists()) {
backupDir.mkpath(".");
}
_assetsDirectory = PathUtils::getAppDataPath() + BACKUPS_DIR + ASSETS_DIR;
QDir assetsDir { _assetsDirectory };
if (!assetsDir.exists()) {
assetsDir.mkpath(".");
}
loadAllBackups();
}
void BackupSupervisor::loadAllBackups() {
_backups.clear();
_assetsInBackups.clear();
_assetsOnDisk.clear();
_allBackupsLoadedSuccessfully = true;
QDir assetsDir { _assetsDirectory };
auto assetNames = assetsDir.entryList(QDir::Files);
qDebug() << "Loading" << assetNames.size() << "assets.";
// store all valid hashes
copy_if(begin(assetNames), end(assetNames),
inserter(_assetsOnDisk, begin(_assetsOnDisk)), AssetUtils::isValidHash);
QDir backupsDir { _backupsDirectory };
auto files = backupsDir.entryList({ MAPPINGS_PREFIX + "*.json" }, QDir::Files);
qDebug() << "Loading" << files.size() << "backups.";
for (const auto& fileName : files) {
auto filePath = backupsDir.filePath(fileName);
auto success = loadBackup(filePath);
if (!success) {
qCritical() << "Failed to load backup file" << filePath;
_allBackupsLoadedSuccessfully = false;
}
}
vector<AssetUtils::AssetHash> missingAssets;
set_difference(begin(_assetsInBackups), end(_assetsInBackups),
begin(_assetsOnDisk), end(_assetsOnDisk),
back_inserter(missingAssets));
if (missingAssets.size() > 0) {
qWarning() << "Found" << missingAssets.size() << "assets missing.";
}
vector<AssetUtils::AssetHash> deprecatedAssets;
set_difference(begin(_assetsOnDisk), end(_assetsOnDisk),
begin(_assetsInBackups), end(_assetsInBackups),
back_inserter(deprecatedAssets));
if (deprecatedAssets.size() > 0) {
qDebug() << "Found" << deprecatedAssets.size() << "assets to delete.";
if (_allBackupsLoadedSuccessfully) {
for (const auto& hash : deprecatedAssets) {
QFile::remove(_assetsDirectory + hash);
}
} else {
qWarning() << "Some backups did not load properly, aborting deleting for safety.";
}
}
}
bool BackupSupervisor::loadBackup(const QString& backupFile) {
_backups.push_back({ backupFile.toStdString(), {}, false });
auto& backup = _backups.back();
QFile file { backupFile };
if (!file.open(QFile::ReadOnly)) {
qCritical() << "Could not open backup file:" << backupFile;
backup.corruptedBackup = true;
return false;
}
QJsonParseError error;
auto document = QJsonDocument::fromJson(file.readAll(), &error);
if (document.isNull() || !document.isObject()) {
qCritical() << "Could not parse backup file to JSON object:" << backupFile;
qCritical() << " Error:" << error.errorString();
backup.corruptedBackup = true;
return false;
}
auto jsonObject = document.object();
for (auto it = begin(jsonObject); it != end(jsonObject); ++it) {
const auto& assetPath = it.key();
const auto& assetHash = it.value().toString();
if (!AssetUtils::isValidHash(assetHash)) {
qCritical() << "Corrupted mapping in backup file" << backupFile << ":" << it.key();
backup.corruptedBackup = true;
return false;
}
backup.mappings[assetPath] = assetHash;
_assetsInBackups.insert(assetHash);
}
_backups.push_back(backup);
return true;
}
void BackupSupervisor::backupAssetServer() {
if (backupInProgress() || restoreInProgress()) {
qWarning() << "There is already a backup/restore in progress.";
return;
}
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createGetAllMappingsRequest();
connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) {
qDebug() << "Got" << request->getMappings().size() << "mappings!";
if (request->getError() != MappingRequest::NoError) {
qCritical() << "Could not complete backup.";
qCritical() << " Error:" << request->getErrorString();
finishBackup();
request->deleteLater();
return;
}
if (!writeBackupFile(request->getMappings())) {
finishBackup();
request->deleteLater();
return;
}
assert(!_backups.empty());
const auto& mappings = _backups.back().mappings;
backupMissingFiles(mappings);
request->deleteLater();
});
startBackup();
request->start();
}
void BackupSupervisor::backupMissingFiles(const AssetUtils::Mappings& mappings) {
_assetsLeftToRequest.reserve(mappings.size());
for (auto& mapping : mappings) {
const auto& hash = mapping.second;
if (_assetsOnDisk.find(hash) == end(_assetsOnDisk)) {
_assetsLeftToRequest.push_back(hash);
}
}
backupNextMissingFile();
}
void BackupSupervisor::backupNextMissingFile() {
if (_assetsLeftToRequest.empty()) {
finishBackup();
return;
}
auto hash = _assetsLeftToRequest.back();
_assetsLeftToRequest.pop_back();
auto assetClient = DependencyManager::get<AssetClient>();
auto assetRequest = assetClient->createRequest(hash);
connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) {
if (request->getError() == AssetRequest::NoError) {
qDebug() << "Got" << request->getHash();
bool success = writeAssetFile(request->getHash(), request->getData());
if (!success) {
qCritical() << "Failed to write asset file" << request->getHash();
}
} else {
qCritical() << "Failed to backup asset" << request->getHash();
}
backupNextMissingFile();
request->deleteLater();
});
assetRequest->start();
}
bool BackupSupervisor::writeBackupFile(const AssetUtils::AssetMappings& mappings) {
auto filename = MAPPINGS_PREFIX + QDateTime::currentDateTimeUtc().toString(Qt::ISODate) + ".json";
QFile file { PathUtils::getAppDataPath() + BACKUPS_DIR + filename };
if (!file.open(QFile::WriteOnly)) {
qCritical() << "Could not open backup file" << file.fileName();
return false;
}
AssetServerBackup backup;
QJsonObject jsonObject;
for (auto& mapping : mappings) {
backup.mappings[mapping.first] = mapping.second.hash;
_assetsInBackups.insert(mapping.second.hash);
jsonObject.insert(mapping.first, mapping.second.hash);
}
QJsonDocument document(jsonObject);
file.write(document.toJson());
backup.filePath = file.fileName().toStdString();
_backups.push_back(backup);
return true;
}
bool BackupSupervisor::writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data) {
QDir assetsDir { _assetsDirectory };
QFile file { assetsDir.filePath(hash) };
if (!file.open(QFile::WriteOnly)) {
qCritical() << "Could not open backup file" << file.fileName();
return false;
}
file.write(data);
_assetsOnDisk.insert(hash);
return true;
}
void BackupSupervisor::restoreAssetServer(int backupIndex) {
if (backupInProgress() || restoreInProgress()) {
qWarning() << "There is already a backup/restore in progress.";
return;
}
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createGetAllMappingsRequest();
connect(request, &GetAllMappingsRequest::finished, this, [this, backupIndex](GetAllMappingsRequest* request) {
if (request->getError() == MappingRequest::NoError) {
const auto& newMappings = _backups.at(backupIndex).mappings;
computeServerStateDifference(request->getMappings(), newMappings);
restoreAllAssets();
} else {
finishRestore();
}
request->deleteLater();
});
startRestore();
request->start();
}
void BackupSupervisor::computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings,
const AssetUtils::Mappings& newMappings) {
_mappingsLeftToSet.reserve((int)newMappings.size());
_assetsLeftToUpload.reserve((int)newMappings.size());
_mappingsLeftToDelete.reserve((int)currentMappings.size());
set<AssetUtils::AssetHash> currentAssets;
for (const auto& currentMapping : currentMappings) {
const auto& currentPath = currentMapping.first;
const auto& currentHash = currentMapping.second.hash;
if (newMappings.find(currentPath) == end(newMappings)) {
_mappingsLeftToDelete.push_back(currentPath);
}
currentAssets.insert(currentHash);
}
for (const auto& newMapping : newMappings) {
const auto& newPath = newMapping.first;
const auto& newHash = newMapping.second;
auto it = currentMappings.find(newPath);
if (it == end(currentMappings) || it->second.hash != newHash) {
_mappingsLeftToSet.push_back({ newPath, newHash });
}
if (currentAssets.find(newHash) == end(currentAssets)) {
_assetsLeftToUpload.push_back(newHash);
}
}
qDebug() << "Mappings to set:" << _mappingsLeftToSet.size();
qDebug() << "Mappings to del:" << _mappingsLeftToDelete.size();
qDebug() << "Assets to upload:" << _assetsLeftToUpload.size();
}
void BackupSupervisor::restoreAllAssets() {
restoreNextAsset();
}
void BackupSupervisor::restoreNextAsset() {
if (_assetsLeftToUpload.empty()) {
updateMappings();
return;
}
auto hash = _assetsLeftToUpload.back();
_assetsLeftToUpload.pop_back();
auto assetFilename = _assetsDirectory + hash;
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createUpload(assetFilename);
connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) {
if (request->getError() != AssetUpload::NoError) {
qCritical() << "Failed to restore asset:" << request->getFilename();
qCritical() << " Error:" << request->getErrorString();
}
restoreNextAsset();
request->deleteLater();
});
request->start();
}
void BackupSupervisor::updateMappings() {
auto assetClient = DependencyManager::get<AssetClient>();
for (const auto& mapping : _mappingsLeftToSet) {
auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second);
connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) {
if (request->getError() != MappingRequest::NoError) {
qCritical() << "Failed to set mapping:" << request->getPath();
qCritical() << " Error:" << request->getErrorString();
}
if (--_mappingRequestsInFlight == 0) {
finishRestore();
}
request->deleteLater();
});
request->start();
++_mappingRequestsInFlight;
}
_mappingsLeftToSet.clear();
auto request = assetClient->createDeleteMappingsRequest(_mappingsLeftToDelete);
connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) {
if (request->getError() != MappingRequest::NoError) {
qCritical() << "Failed to delete mappings";
qCritical() << " Error:" << request->getErrorString();
}
if (--_mappingRequestsInFlight == 0) {
finishRestore();
}
request->deleteLater();
});
_mappingsLeftToDelete.clear();
request->start();
++_mappingRequestsInFlight;
}
bool BackupSupervisor::deleteBackup(int backupIndex) {
if (backupInProgress() || restoreInProgress()) {
qWarning() << "There is a backup/restore in progress.";
return false;
}
const auto& filePath = _backups.at(backupIndex).filePath;
auto success = QFile::remove(filePath.c_str());
loadAllBackups();
return success;
}

View file

@ -0,0 +1,85 @@
//
// BackupSupervisor.h
// domain-server/src
//
// Created by Clement Brisset on 1/12/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_BackupSupervisor_h
#define hifi_BackupSupervisor_h
#include <set>
#include <map>
#include <QObject>
#include <AssetUtils.h>
#include <ReceivedMessage.h>
struct AssetServerBackup {
std::string filePath;
AssetUtils::Mappings mappings;
bool corruptedBackup;
};
class BackupSupervisor : public QObject {
Q_OBJECT
public:
BackupSupervisor();
void backupAssetServer();
void restoreAssetServer(int backupIndex);
bool deleteBackup(int backupIndex);
const std::vector<AssetServerBackup>& getBackups() const { return _backups; };
bool backupInProgress() const { return _backupInProgress; }
bool restoreInProgress() const { return _restoreInProgress; }
private:
void loadAllBackups();
bool loadBackup(const QString& backupFile);
void startBackup() { _backupInProgress = true; }
void finishBackup() { _backupInProgress = false; }
void backupMissingFiles(const AssetUtils::Mappings& mappings);
void backupNextMissingFile();
bool writeBackupFile(const AssetUtils::AssetMappings& mappings);
bool writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data);
void startRestore() { _restoreInProgress = true; }
void finishRestore() { _restoreInProgress = false; }
void computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings,
const AssetUtils::Mappings& newMappings);
void restoreAllAssets();
void restoreNextAsset();
void updateMappings();
QString _backupsDirectory;
QString _assetsDirectory;
// Internal storage for backups on disk
bool _allBackupsLoadedSuccessfully { false };
std::vector<AssetServerBackup> _backups;
std::set<AssetUtils::AssetHash> _assetsInBackups;
std::set<AssetUtils::AssetHash> _assetsOnDisk;
// Internal storage for backup in progress
bool _backupInProgress { false };
std::vector<AssetUtils::AssetHash> _assetsLeftToRequest;
// Internal storage for restore in progress
bool _restoreInProgress { false };
std::vector<AssetUtils::AssetHash> _assetsLeftToUpload;
std::vector<std::pair<AssetUtils::AssetPath, AssetUtils::AssetHash>> _mappingsLeftToSet;
AssetUtils::AssetPathList _mappingsLeftToDelete;
int _mappingRequestsInFlight { 0 };
};
#endif /* hifi_BackupSupervisor_h */

View file

@ -26,6 +26,7 @@
#include <QCommandLineParser>
#include <AccountManager.h>
#include <AssetClient.h>
#include <BuildInfo.h>
#include <DependencyManager.h>
#include <HifiConfigVariantMap.h>
@ -343,6 +344,12 @@ void DomainServer::parseCommandLine() {
DomainServer::~DomainServer() {
qInfo() << "Domain Server is shutting down.";
// cleanup the AssetClient thread
DependencyManager::destroy<AssetClient>();
_assetClientThread.quit();
_assetClientThread.wait();
// destroy the LimitedNodeList before the DomainServer QCoreApplication is down
DependencyManager::destroy<LimitedNodeList>();
}
@ -494,7 +501,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
// store the new domain ID and auto network setting immediately
QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id);
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object());
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings);
// store the new ID and auto networking setting on disk
_settingsManager.persistToFile();
@ -684,11 +691,17 @@ void DomainServer::setupNodeListAndAssignments() {
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK");
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
_assetClientThread.setObjectName("AssetClient Thread");
auto assetClient = DependencyManager::set<AssetClient>();
assetClient->moveToThread(&_assetClientThread);
QObject::connect(&_assetClientThread, &QThread::started, assetClient.data(), &AssetClient::init);
_assetClientThread.start();
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
}
bool DomainServer::resetAccountManagerAccessToken() {

View file

@ -18,6 +18,7 @@
#include <QtCore/QQueue>
#include <QtCore/QSharedPointer>
#include <QtCore/QStringList>
#include <QtCore/QThread>
#include <QtCore/QUrl>
#include <QAbstractNativeEventFilter>
@ -25,6 +26,7 @@
#include <HTTPSConnection.h>
#include <LimitedNodeList.h>
#include "BackupSupervisor.h"
#include "DomainGatekeeper.h"
#include "DomainMetadata.h"
#include "DomainServerSettingsManager.h"
@ -251,6 +253,8 @@ private:
bool _sendICEServerAddressToMetaverseAPIRedo { false };
QHash<QUuid, QPointer<HTTPSConnection>> _pendingOAuthConnections;
QThread _assetClientThread;
};

View file

@ -39,8 +39,11 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
const QString DESCRIPTION_GROUP_LABEL_KEY = "label";
const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup";
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
const QString DESCRIPTION_COLUMNS_KEY = "columns";
const QString CONTENT_SETTING_FLAG_KEY = "content_setting";
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
@ -63,6 +66,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
splitSettingsDescription();
return;
}
}
@ -78,11 +83,99 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE));
}
void DomainServerSettingsManager::splitSettingsDescription() {
// construct separate description arrays for domain settings and content settings
// since they are displayed on different pages
// along the way we also construct one object that holds the groups separated by domain settings
// and content settings, so that the DS can setup dropdown menus below "Content" and "Settings"
// headers to jump directly to a settings group on the page of either
QJsonArray domainSettingsMenuGroups;
QJsonArray contentSettingsMenuGroups;
foreach(const QJsonValue& group, _descriptionArray) {
QJsonObject groupObject = group.toObject();
static const QString HIDDEN_GROUP_KEY = "hidden";
bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool();
QJsonArray domainSettingArray;
QJsonArray contentSettingArray;
foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
QJsonObject settingDescriptionObject = settingDescription.toObject();
bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY)
&& settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool();
if (isContentSetting) {
// push the setting description to the pending content setting array
contentSettingArray.push_back(settingDescriptionObject);
} else {
// push the setting description to the pending domain setting array
domainSettingArray.push_back(settingDescriptionObject);
}
}
if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) {
// we know for sure we'll have something to add to our settings menu groups
// so setup that object for the group now, as long as the group isn't hidden alltogether
QJsonObject settingsDropdownGroup;
if (!groupHidden) {
if (groupObject.contains(DESCRIPTION_NAME_KEY)) {
settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY];
}
settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY];
static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id";
if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) {
settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY];
}
}
if (!domainSettingArray.isEmpty()) {
// we have some domain settings from this group, add the group with the filtered settings
QJsonObject filteredGroupObject = groupObject;
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray;
_domainSettingsDescription.push_back(filteredGroupObject);
// if the group isn't hidden, add its information to the domain settings menu groups
if (!groupHidden) {
domainSettingsMenuGroups.push_back(settingsDropdownGroup);
}
}
if (!contentSettingArray.isEmpty()) {
// we have some content settings from this group, add the group with the filtered settings
QJsonObject filteredGroupObject = groupObject;
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray;
_contentSettingsDescription.push_back(filteredGroupObject);
// if the group isn't hidden, add its information to the content settings menu groups
if (!groupHidden) {
contentSettingsMenuGroups.push_back(settingsDropdownGroup);
}
}
}
}
// populate the settings menu groups with what we've collected
static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
_settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups;
_settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;
}
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) {
Assignment::Type type;
message->readPrimitive(&type);
QJsonObject responseObject = responseObjectForType(QString::number(type));
QJsonObject responseObject = settingsResponseObjectForType(QString::number(type));
auto json = QJsonDocument(responseObject).toJson();
auto packetList = NLPacketList::create(PacketType::DomainSettings, QByteArray(), true, true);
@ -314,14 +407,14 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH);
if (avatarMinScale) {
float scale = avatarMinScale->toFloat();
_configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true);
*newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT;
}
QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH);
if (avatarMaxScale) {
float scale = avatarMaxScale->toFloat();
_configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true);
*newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT;
}
}
@ -986,48 +1079,246 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
}
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
if (connection->requestOperation() == QNetworkAccessManager::PostOperation) {
static const QString SETTINGS_RESTORE_PATH = "/settings/restore";
// we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// store whatever the current _settingsMap is to file
persistToFile();
SettingsType endpointType = url.path() == SETTINGS_PATH_JSON ? DomainSettings : ContentSettings;
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
// we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
if (restartRequired) {
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
} else {
unpackPermissions();
apiRefreshGroupInformation();
emit updateNodePermissions();
emit settingsUpdated();
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
if (restartRequired) {
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
} else {
unpackPermissions();
apiRefreshGroupInformation();
emit updateNodePermissions();
emit settingsUpdated();
}
return true;
} else if (url.path() == SETTINGS_RESTORE_PATH) {
// this is an JSON settings file restore, ask the HTTPConnection to parse the data
QList<FormData> formData = connection->parseFormData();
bool wasRestoreSuccessful = false;
if (formData.size() > 0 && formData[0].second.size() > 0) {
// take the posted file and convert it to a QJsonObject
auto postedDocument = QJsonDocument::fromJson(formData[0].second);
if (postedDocument.isObject()) {
wasRestoreSuccessful = restoreSettingsFromObject(postedDocument.object(), DomainSettings);
}
}
if (wasRestoreSuccessful) {
// respond with a 200 for success
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
} else {
// respond with a 400 for failure
connection->respond(HTTPConnection::StatusCode400);
}
return true;
}
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
static const QString SETTINGS_MENU_GROUPS_PATH = "/settings-menu-groups.json";
static const QString SETTINGS_BACKUP_PATH = "/settings/backup.json";
return true;
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
QJsonObject rootObject;
bool forDomainSettings = (url.path() == SETTINGS_PATH_JSON);
bool forContentSettings = (url.path() == CONTENT_SETTINGS_PATH_JSON);;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = forDomainSettings
? _domainSettingsDescription : _contentSettingsDescription;
// grab a domain settings object for all types, filtered for the right class of settings
// and exclude default values
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = settingsResponseObjectForType("", true,
forDomainSettings, forContentSettings,
true);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
return true;
} else if (url.path() == SETTINGS_MENU_GROUPS_PATH) {
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json");
return true;
} else if (url.path() == SETTINGS_BACKUP_PATH) {
// grab the settings backup as an authenticated user
// for the domain settings type only, excluding hidden and default values
auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false, true);
// setup headers that tell the client to download the file wth a special name
Headers downloadHeaders;
downloadHeaders.insert("Content-Transfer-Encoding", "binary");
// create a timestamped filename for the backup
const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
auto backupFilename = "domain-settings_" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json";
downloadHeaders.insert("Content-Disposition",
QString("attachment; filename=\"%1\"").arg(backupFilename).toLocal8Bit());
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(currentDomainSettingsJSON).toJson(),
"application/force-download", downloadHeaders);
}
}
return false;
}
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) {
QJsonArray* filteredDescriptionArray = settingsType == DomainSettings
? &_domainSettingsDescription : &_contentSettingsDescription;
// grab a copy of the current config before restore, so that we can back out if something bad happens during
QVariantMap preRestoreConfig = _configMap.getConfig();
bool shouldCancelRestore = false;
// enumerate through the settings in the description
// if we have one in the restore then use it, otherwise clear it from current settings
foreach(const QJsonValue& descriptionGroupValue, *filteredDescriptionArray) {
QJsonObject descriptionGroupObject = descriptionGroupValue.toObject();
QString groupKey = descriptionGroupObject[DESCRIPTION_NAME_KEY].toString();
QJsonArray descriptionGroupSettings = descriptionGroupObject[DESCRIPTION_SETTINGS_KEY].toArray();
// grab the matching group from the restore so we can look at its settings
QJsonObject restoreGroup;
QVariantMap* configGroupMap = nullptr;
if (groupKey.isEmpty()) {
// this is for a setting at the root, use the full object as our restore group
restoreGroup = settingsToRestore;
// the variant map for this "group" is just the config map since there's no group
configGroupMap = &_configMap.getConfig();
} else {
if (settingsToRestore.contains(groupKey)) {
restoreGroup = settingsToRestore[groupKey].toObject();
}
// grab the variant for the group
auto groupMapVariant = _configMap.valueForKeyPath(groupKey);
// if it existed, double check that it is a map - any other value is unexpected and should cancel a restore
if (groupMapVariant) {
if (groupMapVariant->canConvert<QVariantMap>()) {
configGroupMap = static_cast<QVariantMap*>(groupMapVariant->data());
} else {
shouldCancelRestore = true;
break;
}
}
}
foreach(const QJsonValue& descriptionSettingValue, descriptionGroupSettings) {
QJsonObject descriptionSettingObject = descriptionSettingValue.toObject();
// we'll override this setting with the default or what is in the restore as long as
// it isn't specifically excluded from backups
bool isBackedUpSetting = !descriptionSettingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY)
|| descriptionSettingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool();
if (isBackedUpSetting) {
QString settingName = descriptionSettingObject[DESCRIPTION_NAME_KEY].toString();
// check if we have a matching setting for this in the restore
QJsonValue restoreValue;
if (restoreGroup.contains(settingName)) {
restoreValue = restoreGroup[settingName];
}
// we should create the value for this key path in our current config map
// if we had value in the restore file
bool shouldCreateIfMissing = !restoreValue.isNull();
// get a QVariant pointer to this setting in our config map
QString fullSettingKey = !groupKey.isEmpty()
? groupKey + "." + settingName : settingName;
QVariant* variantValue = _configMap.valueForKeyPath(fullSettingKey, shouldCreateIfMissing);
if (restoreValue.isNull()) {
if (variantValue && !variantValue->isNull() && configGroupMap) {
// we didn't have a value to restore, but there might be a value in the config map
// so we need to remove the value in the config map which will set it back to the default
qDebug() << "Removing" << fullSettingKey << "from settings since it is not in the restored JSON";
configGroupMap->remove(settingName);
}
} else {
// we have a value to restore, use update setting to set it
// we might need to re-grab config group map in case it didn't exist when we looked for it before
// but was created by the call to valueForKeyPath before
if (!configGroupMap) {
auto groupMapVariant = _configMap.valueForKeyPath(groupKey);
if (groupMapVariant && groupMapVariant->canConvert<QVariantMap>()) {
configGroupMap = static_cast<QVariantMap*>(groupMapVariant->data());
} else {
shouldCancelRestore = true;
break;
}
}
qDebug() << "Updating setting" << fullSettingKey << "from restored JSON";
updateSetting(settingName, restoreValue, *configGroupMap, descriptionSettingObject);
}
}
}
if (shouldCancelRestore) {
break;
}
}
if (shouldCancelRestore) {
// if we cancelled the restore, go back to our state before and return false
qDebug() << "Restore cancelled, settings have not been changed";
_configMap.getConfig() = preRestoreConfig;
return false;
} else {
// restore completed, persist the new settings
qDebug() << "Restore completed, persisting restored settings to file";
persistToFile();
return true;
}
}
QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated,
bool includeDomainSettings,
bool includeContentSettings,
bool includeDefaults, bool isForBackup) {
QJsonObject responseObject;
if (!typeValue.isEmpty() || isAuthenticated) {
@ -1036,8 +1327,16 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
// enumerate the groups in the description object to find which settings to pass
foreach(const QJsonValue& groupValue, _descriptionArray) {
// only enumerate the requested settings type (domain setting or content setting)
QJsonArray* filteredDescriptionArray = &_descriptionArray;
if (includeDomainSettings && !includeContentSettings) {
filteredDescriptionArray = &_domainSettingsDescription;
} else if (includeContentSettings && !includeDomainSettings) {
filteredDescriptionArray = &_contentSettingsDescription;
}
// enumerate the groups in the potentially filtered object to find which settings to pass
foreach(const QJsonValue& groupValue, *filteredDescriptionArray) {
QJsonObject groupObject = groupValue.toObject();
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
@ -1045,11 +1344,17 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
QJsonObject groupResponseObject;
foreach(const QJsonValue& settingValue, groupSettingsArray) {
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
QJsonObject settingObject = settingValue.toObject();
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
// consider this setting as long as it isn't hidden
// and either this isn't for a backup or it's a value included in backups
bool includedInBackups = !settingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY)
|| settingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool();
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool() && (!isForBackup || includedInBackups)) {
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
if (affectedTypesArray.isEmpty()) {
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
@ -1057,8 +1362,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
if (affectedTypesArray.contains(queryType) ||
(queryType.isNull() && isAuthenticated)) {
// this is a setting we should include in the responseObject
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
// we need to check if the settings map has a value for this setting
@ -1074,28 +1377,31 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
variantValue = _configMap.value(settingName);
}
QJsonValue result;
// final check for inclusion
// either we include default values or we don't but this isn't a default value
if (includeDefaults || !variantValue.isNull()) {
QJsonValue result;
if (variantValue.isNull()) {
// no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
result = settingObject[SETTING_DEFAULT_KEY];
if (variantValue.isNull()) {
// no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
result = settingObject[SETTING_DEFAULT_KEY];
} else {
// users are allowed not to provide a default for string values
// if so we set to the empty string
result = QString("");
}
} else {
// users are allowed not to provide a default for string values
// if so we set to the empty string
result = QString("");
result = QJsonValue::fromVariant(variantValue);
}
} else {
result = QJsonValue::fromVariant(variantValue);
}
if (!groupKey.isEmpty()) {
// this belongs in the group object
groupResponseObject[settingName] = result;
} else {
// this is a value that should be at the root
responseObject[settingName] = result;
if (!groupKey.isEmpty()) {
// this belongs in the group object
groupResponseObject[settingName] = result;
} else {
// this is a value that should be at the root
responseObject[settingName] = result;
}
}
}
}
@ -1108,7 +1414,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
}
}
return responseObject;
}
@ -1140,6 +1445,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
settingMap[key] = sanitizedValue;
}
}
} else if (newValue.isDouble()) {
settingMap[key] = newValue.toDouble();
} else if (newValue.isBool()) {
settingMap[key] = newValue.toBool();
} else if (newValue.isObject()) {
@ -1212,7 +1519,8 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
return QJsonObject();
}
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
SettingsType settingsType) {
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
@ -1222,6 +1530,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
auto& settingsVariant = _configMap.getConfig();
bool needRestart = false;
auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription;
// Iterate on the setting groups
foreach(const QString& rootKey, postedObject.keys()) {
const QJsonValue& rootValue = postedObject[rootKey];
@ -1236,7 +1546,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
QJsonObject groupDescriptionObject;
// we need to check the description array to see if this is a root setting or a group setting
foreach(const QJsonValue& groupValue, _descriptionArray) {
foreach(const QJsonValue& groupValue, filteredDescriptionArray) {
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
// we matched a group - keep this since we'll use it below to update the settings
groupDescriptionObject = groupValue.toObject();
@ -1257,7 +1567,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
foreach(const QJsonValue& groupValue, _descriptionArray) {
// find groups with root values (they don't have a group name)
QJsonObject groupObject = groupValue.toObject();
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
// this is a group with root values - check if our setting is in here
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
@ -1269,6 +1581,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
needRestart = true;
@ -1286,6 +1599,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
settingKey == AC_SUBNET_WHITELIST_KEY) {

View file

@ -13,6 +13,7 @@
#define hifi_DomainServerSettingsManager_h
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply>
@ -28,6 +29,7 @@ const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
@ -38,6 +40,10 @@ const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
enum SettingsType {
DomainSettings,
ContentSettings
};
class DomainServerSettingsManager : public QObject {
Q_OBJECT
@ -123,8 +129,11 @@ private slots:
private:
QStringList _argumentList;
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
QJsonArray filteredDescriptionArray(bool isContentSettings);
QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false,
bool includeDomainSettings = true, bool includeContentSettings = true,
bool includeDefaults = true, bool isForBackup = false);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);
@ -132,8 +141,17 @@ private:
void sortPermissions();
void persistToFile();
void splitSettingsDescription();
bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType);
double _descriptionVersion;
QJsonArray _descriptionArray;
QJsonArray _domainSettingsDescription;
QJsonArray _contentSettingsDescription;
QJsonObject _settingsMenuGroups;
HifiConfigVariantMap _configMap;
friend class DomainServer;

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME})
file(GLOB_RECURSE QML_SRC resources/qml/*.qml resources/qml/*.js)
add_custom_target(qml SOURCES ${QML_SRC})
add_custom_target(qmls SOURCES ${QML_SRC})
GroupSources("resources/qml")
function(JOIN VALUES GLUE OUTPUT)
@ -204,13 +204,14 @@ endif()
# link required hifi libraries
link_hifi_libraries(
shared octree ktx gpu gl procedural graphics render
shared task octree ktx gpu gl procedural graphics render
pointers
recording fbx networking model-networking entities avatars trackers
audio audio-client animation script-engine physics
render-utils entities-renderer avatars-renderer ui auto-updater midi
render-utils entities-renderer avatars-renderer ui qml auto-updater midi
controllers plugins image trackers
ui-plugins display-plugins input-plugins
workload
${PLATFORM_GL_BACKEND}
)

View file

@ -88,8 +88,8 @@
]
},
{ "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
{ "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },

View file

@ -0,0 +1,148 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by Fontastic.me</metadata>
<defs>
<font id="hifi-glyphs" horiz-adv-x="512">
<font-face font-family="hifi-glyphs" units-per-em="512" ascent="480" descent="-32"/>
<missing-glyph horiz-adv-x="512" />
<glyph glyph-name="hmd" unicode="&#98;" d="M381 139l-70 0c-18 0-30 17-40 33-4 6-11 16-15 18-4-2-10-12-15-18-11-15-23-33-43-33l-67 0c-53 0-97 42-97 95l0 45c0 53 44 96 97 96l250 0c53 0 96-43 96-96l0-45c0-52-43-95-96-95z m-125 77c16 0 26-15 36-30 5-7 15-22 19-22l70 0c39 0 71 32 71 70l0 45c0 39-32 70-71 70l-250 0c-39 0-71-31-71-70l0-45c0-38 32-70 71-70l67 0c6 0 16 14 22 23 10 14 20 29 35 29 1 0 1 0 1 0z"/>
<glyph glyph-name="2d-screen" unicode="&#99;" d="M395 386l-276 0c-33 0-60-28-60-61l0-116c0-33 27-62 60-62l127 0 0-27-80 0c-8 0-14-5-14-13 0-8 7-13 14-13l186 0c7 0 13 5 13 13 0 8-6 13-13 13l-81 0 0 27 124 0c33 0 60 29 60 62l0 116c0 33-27 61-60 61z m32-177c0-18-14-33-32-33l-134 0c-1 0-1 0-2 0-1 0-2 0-2 0l-138 0c-18 0-32 15-32 33l0 116c0 18 14 33 32 33l276 0c18 0 32-15 32-33z"/>
<glyph glyph-name="keyboard" unicode="&#100;" d="M375 250l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m-36 0l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m-36 0l-26 0 0 25 26 0z m225-1l17 0c7 0 13 6 13 13 0 8-6 14-13 14l-18 0m-262 0l-17 0c-7 0-13-6-13-14 0-7 6-13 13-13l18 0m251 39l-31 0 0 25 31 0z m-41 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-42 0l-31 0 0 25 31 0z m-41 0l-32 0 0 25 32 0z m219 0l17 0c7 0 13 6 13 13 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-7 6-13 13-13l18 0m287-124l-315 0c-32 0-59 27-59 60l0 77c0 33 27 60 59 60l315 0c33 0 60-27 60-60l0-77c0-33-27-60-60-60z m-315 171c-18 0-33-15-33-34l0-77c0-19 15-34 33-34l315 0c19 0 34 16 34 34l0 77c0 19-15 34-34 34z m249-99l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m251-25l17 0c7 0 13 6 13 13 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-7 6-13 13-13l18 0"/>
<glyph glyph-name="hand-controllers" unicode="&#101;" d="M141 268l-3 0c-7 0-13 5-13 13 0 7 6 13 13 13l3 0c7 0 13-6 13-13 0-8-6-13-13-13z m28 26l-3 0c-7 0-13 6-13 13 0 7 6 13 13 13l3 0c7 0 13-6 13-13 0-7-6-13-13-13z m-12-169l-11 0c-31 0-50 23-50 60l-10 143c0 34 27 61 60 61l11 0c33 0 60-27 60-60l0-1-10-144c0-36-20-59-50-59z m-11 238c-19 0-34-15-34-34l10-143c0-14 3-35 24-35l11 0c21 0 24 21 24 34l10 144c-1 19-16 34-34 34z m203-69c-7 0-13 5-13 13l0 2c0 8 6 13 13 13 7 0 13-5 13-13l0-2c0-7-6-13-13-13z m27-29c-8 0-13 6-13 13l0 3c0 7 5 13 13 13 7 0 13-6 13-13l0-3c0-7-6-13-13-13z m-11-140l-10 0c-31 0-51 23-51 60l-9 143c0 34 27 61 60 61l10 0c33 0 60-27 60-60l0-1-9-144c0-36-20-59-51-59z m-10 238c-19 0-34-15-34-34l9-143c0-14 4-35 25-35l11 0c21 0 24 21 24 34l9 144c0 19-15 34-33 34z"/>
<glyph glyph-name="headphones-mic" unicode="&#102;" d="M419 348l-22 0c-3 48-42 83-89 83l-105 0c-47 0-86-35-89-83l-20 0c-25 0-45-19-45-44l0-71c0-25 20-45 45-45l19 0c1-27 14-50 33-66-3-17 5-35 20-45l41-25c7-4 15-7 23-7 3 0 6 1 10 2 11 2 21 9 27 19 13 21 6 48-14 60l-41 26c-10 6-21 8-33 5-8-2-15-6-20-11-12 11-20 27-20 45l0 152c0 34 29 62 64 62l105 0c35 0 64-28 64-62l0-152c0-1-1-3-1-3l48 0c25 0 46 20 46 45l0 71c0 25-21 44-46 44z m-306-134l-19 0c-10 0-19 9-19 19l0 71c0 10 9 19 19 19l19 0z m61-90c3 4 7 6 11 7 2 1 3 1 4 1 4 0 7-1 9-3l41-25c8-5 11-16 6-24-3-4-7-7-11-8-5-1-10 0-14 2l-40 25c-8 6-11 16-6 25z m264 109c0-10-8-19-19-19l-22 0 0 109 22 0c11 0 19-9 19-19z"/>
<glyph glyph-name="gamepad" unicode="&#103;" d="M107 136c-10 0-20 3-29 10-46 37-8 131-4 141l1 1c1 4 3 7 5 11 16 30 37 73 81 73l182 0c51 0 71-47 87-85 5-10 44-101 4-138-28-26-67-1-102 22-21 13-42 26-56 26l-39 0c-13 0-33-13-53-27-25-16-52-34-77-34z m-10 141c-10-24-30-90-3-112 17-13 47 7 76 26 24 16 47 31 67 31l40 0c20 0 44-15 68-30 28-18 59-37 72-25 23 22 0 89-10 110-17 42-31 70-64 70l-182 0c-29 0-45-33-59-60-2-3-3-7-5-10z m247-36l-3 0c-7 0-13 6-13 13 0 7 6 13 13 13l3 0c7 0 13-6 13-13 0-7-6-13-13-13z m0 55l-3 0c-7 0-13 6-13 13 0 7 6 13 13 13l3 0c7 0 13-6 13-13 0-7-6-13-13-13z m-29-29l-2 0c-8 0-13 6-13 13 0 8 5 13 13 13l2 0c8 0 13-5 13-13 0-7-5-13-13-13z m57 0l-3 0c-7 0-13 6-13 13 0 8 6 13 13 13l3 0c7 0 13-5 13-13 0-7-6-13-13-13z m-172 26l-12 0 0 13c0 7-6 13-13 13-7 0-13-6-13-13l0-13-13 0c-7 0-13-6-13-13 0-7 6-13 13-13l13 0 0-12c0-8 6-14 13-14 7 0 13 6 13 14l0 12 12 0c8 0 13 6 13 13 0 7-5 13-13 13z"/>
<glyph glyph-name="headphones" unicode="&#104;" d="M141 168l0 152c0 35 28 65 63 65l106 0c34 0 62-30 62-65l0-151c0-1 1-3 0-4l48 0c25 0 47 21 47 46l0 70c0 25-22 45-47 45l-21 0c-4 47-42 83-89 83l-106 0c-47 0-86-36-89-83l-19 0c-25 0-45-20-45-45l0-70c0-25 20-46 45-46l45 0z m-63 43l0 70c0 11 7 19 18 19l19 0 0-108-19 0c-11 0-18 8-18 19z m362 0c0-11-9-19-20-19l-20 0 0 108 20 0c11 0 20-8 20-19z"/>
<glyph glyph-name="mic" unicode="&#105;" d="M318 370c0 33-26 59-59 59l-6 0c-33 0-59-26-59-59l0-105c0-33 26-59 59-59l6 0c33 0 59 26 59 59z m-25-103c0-19-15-34-34-34l-7 0c-19 0-34 15-34 34l0 104c0 19 15 34 34 34l7 0c19 0 34-15 34-34z m82 8c0 7-6 13-12 13-7 0-13-6-13-13 0-51-42-93-93-93-52 0-93 42-93 93 0 7-6 13-13 13-7 0-12-6-12-13 0-60 46-110 104-117l0-34-80 0c-8 0-14-6-14-14 0-7 6-13 14-13l186 0c7 0 13 6 13 13 0 8-6 14-13 14l-80 0 0 34c60 6 106 56 106 117z"/>
<glyph glyph-name="upload" unicode="&#106;" d="M330 193l-83 86-84-86 52 0 0-141 61 23 0 118z m-12 247c-39 0-76-15-105-41-23-21-40-49-47-80-9 3-19 4-29 4-53 0-97-43-97-97 0-54 44-98 97-98 1 0 19 0 45 0l0 29c-26 0-44 0-45 0-37 0-68 31-68 69 0 37 31 68 68 68 12 0 23-3 34-9l19-11 2 23c3 31 18 59 41 81 23 21 54 33 85 33 70 0 127-57 127-127 0-70-57-127-127-127 0 0-5 0-10 0l0-29c5 0 10 0 10 0 86 0 156 70 156 156 0 86-70 156-156 156z"/>
<glyph glyph-name="script" unicode="&#107;" d="M283 80l-150 0c-30 0-56 15-73 44-13 21-17 42-17 42l-3 15 91 0 0 252 315 0 0-238c1-7 5-58-21-87-13-14-29-21-50-21-42 0-63 23-73 41-6 11-9 21-10 29l-220 0c2-6 5-13 9-20 13-21 31-32 52-32l150 0c7 0 13-6 13-13 0-6-6-12-13-12z m-127 101l158 0 1-12c0 0 1-15 9-30 10-18 27-28 51-28 13 0 23 5 31 13 10 11 14 29 15 42 1 15 0 27 0 27l0 1 0 214-265 0z m225 168l-185 0c-8 0-14 6-14 13 0 8 6 14 14 14l185 0c8 0 14-6 14-14 0-7-6-13-14-13z m0-61l-185 0c-8 0-14 7-14 14 0 8 6 14 14 14l185 0c8 0 14-6 14-14 0-7-6-14-14-14z m0-60l-185 0c-8 0-14 6-14 14 0 7 6 13 14 13l185 0c8 0 14-6 14-13 0-8-6-14-14-14z"/>
<glyph glyph-name="text" unicode="&#108;" d="M220 134l-81 232c-1 2-3 4-6 4l-10 0c-3 0-5-2-6-4l-83-233c-1-2 0-5 1-7 1-1 3-3 6-3l16 0c3 0 5 3 6 5l27 79 74 0 27-79c1-2 4-5 7-5l16 0c2 0 4 2 5 4 2 1 2 4 1 7z m-120 102l26 73c1 2 1 3 2 5 0-2 1-3 2-4l24-74z m252 60c-10 12-25 18-44 18-17 0-35-5-53-14-3-2-4-6-3-9l5-14c1-2 2-3 4-4 2-1 4 0 6 1 14 8 28 12 41 12 11 0 19-3 23-10 5-7 8-18 8-33l0-4-23-1c-25 0-44-6-58-16-14-11-21-26-21-45 0-17 4-31 14-40 10-10 23-16 40-16 12 0 23 3 32 8 6 4 11 8 17 14l1-13c1-4 4-7 7-7l10 0c4 0 8 4 8 8l0 115c0 23-5 39-14 50z m-87-119c0 11 4 19 11 24 8 5 22 9 42 10l19 1 0-10c0-17-4-30-12-39-8-9-19-13-33-13-9 0-16 2-20 7-5 4-7 11-7 20z m186-105c-10 0-15 8-15 18 0 22 0 298 0 320 0 10 5 19 15 19 0 0 0 0 0 0 10 0 15-8 15-18 1-22 1-299 1-321 0-10-6-18-16-18 0 0 0 0 0 0z"/>
<glyph glyph-name="cube" unicode="&#109;" d="M452 421l-263 26c-3 0-6 0-8-2l0 0-126-88c-1-1-1-1-1-1-3-3-4-7-4-10l0-263c0-7 5-12 12-13l262-26c0 0 1 0 1 0 3 0 5 1 8 2l126 89c0 0 0 0 0 1 3 2 4 6 4 9l0 263c0 7-5 12-11 13z m-22-274l-2-1c0 0 0 0 0-1 0 0 0 0 0 0l-89-62 0 230 98 69 0-222c-1-6-2-9-7-13z m-227 272l211-21-92-65-222 22 86 61c1 0 1 0 1 0 0 0 0 0 0 0 3 2 7 4 11 4 2 0 4-1 5-1z m110-347l-237 23 0 236 237-23z"/>
<glyph glyph-name="sphere" unicode="&#110;" d="M418 415c-91 91-237 91-327 0-44-43-68-101-68-163 0-62 24-120 68-164 45-45 104-67 163-67 59 0 119 22 164 67 43 44 68 102 68 164-1 62-25 120-68 163z m-305-22c30 30 67 49 106 55-8-11-14-26-20-44-13-41-20-96-20-154 0-3 0-7 0-10 6-1 12-2 19-3 2 0 4 0 6 0 0 4 0 8 0 13 0 117 30 193 52 200 1 0 1 1 1 1 51 0 100-20 138-58 37-37 58-85 59-137-5-22-82-53-200-53-116 0-192 29-200 52 1 52 22 101 59 138z m283-283c-40-39-92-59-144-58-14 9-30 43-40 97l-25 1c3-19 7-39 12-54 5-16 11-30 17-40-38 7-74 25-103 54-30 30-49 67-56 107 11-7 25-13 42-18 42-14 97-21 155-21 58 0 113 7 154 21 18 5 32 12 43 19-7-41-26-78-55-108z"/>
<glyph glyph-name="zone" unicode="&#111;" d="M381 372c-3 7-9 11-17 11l-72 0 0-34 30 0-163-163 0 36-36 0 0-79 1 0c0-2 1-4 2-6 2-7 9-11 16-11l83 0 0 35-41 0 162 161 0-25 35 0 0 62c1 4 1 9 0 13z m-43-298c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-21-22-21-12 0-22 9-22 21z m-71 0c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-21-21-21-12 0-22 9-22 21z m-72 0c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-21-22-21-12 0-22 9-22 21z m-71 0c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-21-21-21-12 0-22 9-22 21z m-50-21c-6 0-11 2-15 6-4 4-7 10-7 15 0 6 3 12 7 16 4 4 9 6 15 6 6 0 11-2 15-6 4-4 7-10 7-16 0-5-2-11-7-15-4-4-9-6-15-6z m-22 93c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-22-22-22-12 0-22 10-22 22z m0 71c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-21-22-21-12 0-22 9-22 21z m0 72c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-22-22-22-12 0-22 10-22 22z m0 71c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-21-22-21-12 0-22 9-22 21z m286 72c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-22-22-22-12 0-22 10-22 22z m-71 0c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-22-21-22-12 0-22 10-22 22z m-72 0c0 12 10 22 22 22 12 0 22-10 22-22 0-12-10-22-22-22-12 0-22 10-22 22z m-71 0c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-22-21-22-12 0-22 10-22 22z m308-22c-6 0-12 3-16 7-4 4-6 9-6 15 0 6 2 11 6 15 4 4 10 7 16 7 5 0 11-3 15-7 4-4 6-9 6-15 0-6-2-11-6-15-4-4-10-7-15-7z m-22-264c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-22-21-22-12 0-22 10-22 22z m0 71c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-21-21-21-12 0-22 9-22 21z m0 72c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-22-21-22-12 0-22 10-22 22z m0 71c0 12 10 22 22 22 12 0 21-10 21-22 0-12-9-21-21-21-12 0-22 9-22 21z"/>
<glyph glyph-name="light" unicode="&#112;" d="M298 259c0-21-18-38-39-38-21 0-38 17-38 38 0 22 17 39 38 39 21 0 39-17 39-39z m-39-109c-60 0-109 49-109 109 0 61 49 110 109 110 61 0 110-49 110-110 0-60-50-109-110-109z m0 190c-44 0-80-36-80-81 0-44 36-80 80-80 45 0 81 36 81 80 0 45-36 81-81 81z m155-97c-8 0-14 7-14 15 0 8 6 14 14 14 17 0 35 0 52 0 0 0 0 0 0 0 8 0 14-6 14-14 0-8-6-14-14-14-17 0-35 0-52-1 0 0 0 0 0 0z m-308 0c-17 1-35 1-52 1-8 0-14 6-14 14 0 8 7 14 14 14 0 0 0 0 1 0 17 0 34 0 51 0 8 0 14-7 14-14 0-8-6-15-14-15z m263 109c-3 0-7 2-10 5-5 5-5 14 0 20 12 12 24 24 36 36 6 6 15 6 21 1 5-6 5-15 0-21-12-12-25-24-37-36-3-3-6-5-10-5z m-255-254c-3 0-7 2-10 5-5 5-5 14 0 20 13 12 25 24 37 36 6 5 15 5 20 0 6-6 6-15 0-20-12-13-24-25-37-37-2-2-6-4-10-4z m146 300c-8 0-14 6-14 14 0 17 0 34 0 52 0 7 6 14 14 14 0 0 0 0 0 0 8 0 14-6 14-14 0-18 1-35 1-52 0-8-7-14-15-14z m0-360c0 0 0 0 0 0-8 0-14 6-14 14 0 17 0 35 0 52 0 8 6 14 14 14 0 0 0 0 0 0 8 0 15-7 15-14 0-18-1-35-1-52 0-8-6-14-14-14z m-109 315c-3 0-7 1-10 4-12 12-24 24-36 36-6 6-6 15-1 20 6 6 15 6 21 0 12-12 24-24 36-36 6-6 6-15 0-20-2-3-6-4-10-4z m254-255c-3 0-7 1-10 4-12 12-24 25-36 37-5 6-5 15 0 20 6 6 15 6 20 0 13-12 25-25 37-37 5-5 5-14-1-20-2-3-6-4-10-4z"/>
<glyph glyph-name="web" unicode="&#113;" d="M438 390c0 8-6 15-14 15l-333 0c-8 0-15-7-15-15l0-298c0-8 7-14 15-14l333 0c8 0 14 6 14 14z m-219-8l172 0c8 0 15-7 15-16 0-9-7-16-15-16l-172 0c-8 0-15 7-15 16 0 9 7 16 15 16z m-47 1c9 0 16-7 16-16 0-10-7-17-16-17-10 0-17 7-17 17 0 9 7 16 17 16z m-51 0c9 0 17-7 17-16 0-10-8-17-17-17-9 0-17 7-17 17 0 9 8 16 17 16z m291-276l-308 0 0 219 308 0z m-250 84l-15 0-20 60 13 0 14-45 15 45 13 0 15-45 14 45 13 0-20-60-14 0-14 41z m137 25l-46 0c0-5 2-8 6-11 3-2 8-4 12-4 8 0 13 3 17 7l7-8c-6-6-14-9-25-9-8 0-15 2-21 8-6 5-9 13-9 22 0 9 3 17 9 22 6 6 13 9 21 9 8 0 15-3 21-8 6-5 8-11 8-20z m-46 9l34 0c0 5-2 9-5 12-3 3-7 4-11 4-5 0-9-1-13-4-3-3-5-7-5-12z m101 27c8 0 14-3 20-9 6-5 9-12 9-22 0-9-3-16-9-22-5-6-12-8-19-8-8 0-15 3-21 9l0-9-12 0 0 83 12 0 0-34c5 8 12 12 20 12z m-20-31c0-6 2-10 5-14 4-4 8-5 13-5 5 0 9 1 13 5 3 4 5 8 5 14 0 6-2 10-5 14-4 4-8 6-13 6-5 0-9-2-13-6-3-4-5-8-5-14z"/>
<glyph glyph-name="web-2" unicode="&#114;" d="M438 390c0 8-6 15-14 15l-333 0c-8 0-15-7-15-15l0-298c0-8 7-14 15-14l333 0c8 0 14 6 14 14z m-219-8l172 0c8 0 15-7 15-16 0-9-7-16-15-16l-172 0c-8 0-15 7-15 16 0 9 7 16 15 16z m-47 1c9 0 16-7 16-16 0-10-7-17-16-17-10 0-17 7-17 17 0 9 7 16 17 16z m-51 0c9 0 17-7 17-16 0-10-8-17-17-17-9 0-17 7-17 17 0 9 8 16 17 16z m291-276l-308 0 0 219 308 0z"/>
<glyph glyph-name="edit" unicode="&#115;" d="M196 214c-27-27-54-54-81-81-6 6-13 13-19 19 27 27 54 54 81 81l-22 21c-31-31-61-62-92-92-7-7-11-13-12-22-3-25-7-50-11-76 3 0 4 0 6 0 24 5 48 10 72 15 5 1 10 4 13 7 32 32 64 64 96 96z m126 207c10 10 21 21 33 32 4 4 10 4 14 0 19-19 38-38 57-57 4-5 4-10 0-15-11-11-22-22-34-33-23 24-46 48-70 73z m23-181c-5-1-8 0-11 3-8 8-15 15-23 23 18 18 37 37 55 55 2 2 4 4 4 4-24 25-47 49-71 74-2-2-3-4-5-6-18-18-37-37-55-55-34 34-67 67-101 101-2 2-5 5-8 7-18 14-42 12-57-5-15-17-14-42 2-58 50-51 101-101 151-151 17-16 33-33 50-49 2-3 3-5 2-8-2-8-4-15-4-23-4-48 27-90 73-102 20-5 39-4 58 4-1 2-3 3-5 4-14 14-28 28-42 42-10 11-11 26-2 36 8 8 16 16 24 24 11 10 24 10 35 0 2-1 4-4 6-6 15-14 29-28 43-42 0 0 1 0 2 1 1 8 3 16 4 24 7 69-59 123-125 103z m-243 162c-7 0-14 6-14 14 0 8 6 14 14 14 8 0 15-6 15-14 0-7-7-14-15-14z m198-46c6-6 13-12 19-19-13-13-26-26-39-39-7 6-13 12-20 19 14 13 27 26 40 39z"/>
<glyph glyph-name="market" unicode="&#116;" d="M88 370c3 0 7 0 10 0 6 0 11-1 15-2 9-2 16-8 20-16 3-5 4-10 6-15 2-6 3-13 5-19 3-10 5-19 8-27 2-6 3-13 5-19 3-7 5-15 7-23 3-9 6-19 9-30 0-2 1-4 1-6 2-8 5-17 9-24 3-7 8-12 13-15 6-3 13-5 22-6 2 0 5 0 7 0l21 0 57 0c25 0 50 0 75 0 12 0 25 0 37 0 1 0 1 0 2 0 6 0 13 0 17 2 5 3 7 9 9 16l3 10c0 0 0 0 0 0 0 2 1 3 1 3 5 20 11 40 17 60 1 4 2 9 4 13 2 8 4 16 7 24 2 7 4 16 0 22-3 4-9 5-14 5-6 0-188-2-284-4l-4 0-5 22c0 3-1 6-2 9 0 3-1 5-1 7-1 2-1 3-1 4-1 4-2 7-3 11-2 6-6 12-11 17-5 5-11 8-17 9-3 1-6 1-10 1-5 1-9 1-14 1-19 0-38 0-59 0-1 0-3 0-4 0-3 0-7 0-10-1-3 0-6-2-8-4-3-5-4-11-2-17 2-5 6-6 9-7 4-1 8-1 13-1 5 0 10 0 16 0l16 0c3 0 5 0 8 0z m353-78l-26-92-6-4-2 0c-2 0-21 0-55 0-50 0-118-1-125-1l-1-1 0 0c-6 0-12 2-16 6-4 4-6 10-8 16-3 12-6 25-10 39-1 4-2 8-3 12-2 4-3 8-4 13l-4 12z m-194-164c-9 0-15-2-20-7-5-5-8-11-8-20 0-8 3-16 8-21 5-5 13-8 20-8 17 0 30 12 30 28 0 8-3 15-9 20-5 6-12 8-21 8z m139 0c-9 0-16-2-21-6-5-6-8-13-8-21 0-9 3-16 8-21 5-6 12-8 21-8 16 0 29 13 29 29 0 8-3 14-8 20-5 5-13 7-21 7z"/>
<glyph glyph-name="directory" unicode="&#117;" d="M432 451l-99-38c-2 0-3-1-4-1-3 1-5 2-8 2l-116 38c-8 3-15 6-30-1l-91-35c-17-5-32-16-32-31l0-303c0-15 14-27 32-27l99 38c3 1 6 2 9 4 1-1 3-2 4-2 3-1 5-2 7-3 0 0 1 0 1-1 0 0 1 0 1 0l116-38 0 0c8-1 12-1 21 3l90 34c13 6 32 16 32 31l0 304c0 14-14 26-32 26z m-351-371c-1 0-1 1-1 2l0 303c0 2 5 6 14 9l1 0 1 1 89 34c1-1 0-1 0-2l0-303c0-1-3-4-16-10z m356 42c-1-2-5-5-17-11l-90-34c0 1-1 1-1 2l0 304c0 1 5 5 14 8l1 0 1 1 91 34c0 0 1-1 1-1z"/>
<glyph glyph-name="menu" unicode="&#118;" d="M257 22c-60 0-119 22-164 67-44 44-68 102-68 164 0 62 24 120 68 163 90 91 237 91 327 0 44-43 68-101 68-163 0-62-24-120-68-164-45-45-104-67-163-67z m0 431c-52 0-103-20-142-59-38-38-58-88-58-141 0-54 20-104 58-142 78-78 205-78 283 0 38 38 59 88 59 142 0 53-21 103-59 141-39 39-90 59-141 59z m101-133l-203 0c-8 0-15 7-15 15 0 8 7 15 15 15l203 0c8 0 14-7 14-15 0-8-6-15-14-15z m0-84l-203 0c-8 0-15 7-15 15 0 8 7 15 15 15l203 0c8 0 14-7 14-15 0-8-6-15-14-15z m0-81l-203 0c-8 0-15 7-15 15 0 8 7 14 15 14l203 0c8 0 14-6 14-14 0-8-6-15-14-15z"/>
<glyph glyph-name="close" unicode="&#119;" d="M258 19c-59 0-118 23-163 68-44 43-68 101-68 163 0 62 24 120 68 164 90 90 237 90 327 0 44-44 68-102 68-164 0-62-24-120-68-163-45-45-104-68-164-68z m0 431c-51 0-102-19-141-58-38-38-59-88-59-142 0-53 21-103 59-141 78-78 205-78 283 0 38 38 58 88 58 141 0 54-20 104-58 142-39 39-90 58-142 58z m25-200l67 67c7 7 7 18 0 25-7 7-18 7-25 0l-67-67-66 67c-7 7-18 7-25 0-7-7-7-18 0-25l66-67-66-66c-7-7-7-18 0-25 7-7 18-7 25 0l66 66 67-66c7-7 18-7 25 0 7 7 7 18 0 25z"/>
<glyph glyph-name="close-inverted" unicode="&#120;" d="M400 388c-39 39-90 59-142 59-51 0-102-20-141-59-38-37-59-88-59-141 0-53 21-104 59-141 78-78 205-78 283 0 38 37 58 88 58 141 0 53-20 104-58 141z m-45-208c8-9 8-22 0-30-8-8-21-8-29 0l-68 67-67-67c-8-8-21-8-29 0-9 8-9 21 0 30l67 67-67 67c-9 8-9 22 0 30 8 8 21 8 29 0l67-68 68 68c8 8 21 8 29 0 8-9 8-22 0-30l-67-67z"/>
<glyph glyph-name="pin" unicode="&#121;" d="M304 144c1 16-2 31-8 45l97 115 25-8c14-4 29 2 36 14 8 13 6 29-5 39l-116 116c-10 11-26 13-39 5-12-8-18-23-13-37l8-25-115-102c-33 11-72 2-98-24-12-12-12-33 0-45l65-65-83-82c-6-7-6-17 0-23 6-6 16-6 23 0l82 83 66-66c13-13 33-13 45 0 17 17 28 38 30 60z m-205 115c21 22 55 26 81 9l145 130-14 45 116-116-45 13-126-149c25-28 18-62-4-85z"/>
<glyph glyph-name="pin-inverted" unicode="&#122;" d="M297 160c2 14 0 27-6 39l92 106 21-5c13-4 26 2 33 13 8 12 6 26-3 35l-98 99c-9 8-24 10-35 2-11-7-17-21-13-33l6-22-106-96c-30 9-64 0-88-24-12-12-13-30-2-41l55-55-77-76c-5-6-6-15-1-20 6-6 15-5 20 1l77 76 56-56c11-11 29-10 41 2 15 15 25 35 28 55z"/>
<glyph glyph-name="resize-handle" unicode="&#65;" d="M262 175l70 71c7 7 18 7 25 0 7-7 7-18 0-25l-70-71c-7-7-18-7-25 0-7 7-7 18 0 25m-101 0l175 175c7 7 18 7 25 0 7-7 7-18 0-25l-175-175c-7-7-18-7-25 0-7 7-7 18 0 25"/>
<glyph glyph-name="diclosure-expand" unicode="&#66;" d="M239 187c-3 0-6 1-8 3-5 5-5 12 0 17l42 42-43 44c-5 4-5 12 0 17 4 4 12 4 17 0l60-61-60-59c-2-2-5-3-8-3z"/>
<glyph glyph-name="reload-small" unicode="&#97;" d="M334 253c-9 0-18-7-18-16-3-27-25-47-52-47-29 0-52 24-52 52 0 11 2 27 14 38 6 5 14 9 24 12-5-7-4-16 2-22 3-3 8-4 12-4 5 0 9 1 13 5l28 28c1 2 3 4 3 7 3 6 1 13-3 18l-29 29c-3 3-7 5-12 5-5 0-9-2-12-5-4-3-5-8-5-12 0-5 1-9 5-13l0 0c-20-3-37-11-50-23-16-16-25-38-25-63 0-47 39-86 86-86 45 0 82 33 87 78 1 9-6 18-16 19z"/>
<glyph glyph-name="close-small" unicode="&#67;" d="M291 259l43 44c8 7 8 19 0 27-7 7-19 7-26 0l-44-44-44 44c-7 7-19 7-26 0-8-8-8-20 0-27l43-44-43-43c-8-8-8-20 0-27 7-7 19-7 26 0l44 44 44-44c7-7 19-7 26 0 8 8 8 19 0 27z"/>
<glyph glyph-name="backward" unicode="&#69;" d="M292 349c-5 3-12 3-18-1l-94-71c-4-3-7-8-7-13 0-5 2-10 6-14l95-80c3-2 7-4 11-4 2 0 5 1 7 2 6 3 10 9 10 15l0 151c0 6-4 12-10 15"/>
<glyph glyph-name="reload" unicode="&#70;" d="M365 261c-9 1-17-5-18-15-4-45-43-80-89-80-49 0-89 40-89 89 0 19 4 45 25 65 16 15 39 23 68 25l-15-16c-6-6-6-17 0-24 4-3 8-4 12-4 4 0 9 1 12 5l43 44c2 2 3 4 4 6 2 6 1 13-4 18l-44 44c-6 6-17 6-23 0-7-7-7-17 0-24l15-15c-38-2-69-14-91-35-23-21-36-53-36-88 0-68 55-123 123-123 64 0 116 47 122 110 1 9-5 18-15 18"/>
<glyph glyph-name="minimize" unicode="&#73;" d="M154 282l198 0c10 0 18-8 18-18 0-10-8-18-18-18l-198 0c-10 0-18 8-18 18 0 10 8 18 18 18"/>
<glyph glyph-name="maximize" unicode="&#74;" d="M157 244l77 0 0-75c0-9 8-17 17-17 9 0 17 8 17 17l0 75 75 0c10 0 17 8 17 17 0 10-7 18-17 18l-75 0 0 76c0 10-8 17-17 17-9 0-17-7-17-17l0-76-77 0c-10 0-17-8-17-18 0-9 8-17 17-17z"/>
<glyph glyph-name="maximize-inverted" unicode="&#75;" d="M251 434c-96 0-173-78-173-173 0-96 77-173 173-173 95 0 173 77 173 173 0 95-78 173-173 173z m93-190l-77 0 0-76c0-10-7-17-16-17-9 0-16 7-16 17l0 76-77 0c-10 0-17 8-17 17 0 9 7 17 17 17l77 0 0 76c0 9 7 17 16 17 9 0 16-8 16-17l0-76 77 0c9 0 17-8 17-17 0-9-8-17-17-17z"/>
<glyph glyph-name="disclosure-button-expand" unicode="&#76;" d="M264 202l-60 59c-4 5-4 12 0 17 5 4 13 4 17 0l43-43 43 44c4 4 12 4 17 0 4-5 4-13 0-17z m90-79l-188 0c-16 0-29 13-29 29l0 188c0 16 13 29 29 29l188 0c16 0 29-13 29-29l0-188c0-16-13-29-29-29z m-188 222c-3 0-5-2-5-5l0-188c0-3 2-5 5-5l188 0c3 0 5 2 5 5l0 188c0 3-2 5-5 5z"/>
<glyph glyph-name="disclosure-button-collapse" unicode="&#77;" d="M264 290l-60-59c-4-5-4-12 0-17 5-4 13-4 17 0l43 43 43-44c4-4 12-4 17 0 4 5 4 13 0 17z m119 50l0-188c0-16-13-29-29-29l-188 0c-16 0-29 13-29 29l0 188c0 16 13 29 29 29l188 0c16 0 29-13 29-29z m-29-193c3 0 5 2 5 5l0 188c0 3-2 5-5 5l-188 0c-3 0-5-2-5-5l0-188c0-3 2-5 5-5z"/>
<glyph glyph-name="script-stop" unicode="&#78;" d="M298 79l-145 0c-29 0-54 14-71 42-13 20-17 41-17 41l-3 16 267 0 1-13c0 0 1-15 9-29 9-17 26-26 48-26 13 0 23 4 30 12 16 18 16 54 15 66l0 1 0 205-270 0c-7 0-13 7-13 14 0 7 6 14 13 14l294 0 0-232c1-7 5-57-20-86-13-13-29-20-49-20-41 0-61 22-71 41-6 9-9 18-10 27l-211 0c2-7 5-12 9-18 12-20 29-31 49-31l145 0c8 0 13-5 13-12 0-7-5-12-13-12z m95 258l-112 0c-7 0-14 7-14 14 0 8 7 15 14 15l112 0c8 0 14-7 14-15 0-8-6-14-14-14z m0-58l-143 0c-7 0-14 6-14 14 0 8 7 14 14 14l143 0c8 0 14-6 14-14 0-8-6-14-14-14z m0-58l-112 0c-7 0-14 6-14 14 0 8 7 14 14 14l112 0c8 0 14-6 14-14 0-8-6-14-14-14z m-204 72l42 42c7 7 7 19 0 26-7 7-18 7-25 0l-43-42-42 42c-7 7-18 7-26 0-7-7-7-19 0-26l43-42-43-42c-7-7-7-19 0-26 8-7 19-7 26 0l42 42 43-42c7-7 18-7 25 0 7 7 7 19 0 26z"/>
<glyph glyph-name="script-reload" unicode="&#79;" d="M236 295c-10 1-18-6-19-15-2-26-24-45-50-45-28 0-50 22-50 50 0 10 2 25 14 36 5 6 13 10 23 12-4-7-4-15 2-21 3-3 7-5 12-5 4 0 9 2 12 5l27 28c1 2 3 4 3 6 3 7 2 14-3 18l-28 28c-3 3-7 5-11 5-5 0-9-2-12-5-3-3-5-7-5-12 0-4 2-9 5-12l1-1c-20-2-37-10-49-22-16-14-25-36-25-60 0-46 38-84 84-84 43 0 79 33 83 76 1 9-5 17-14 18z m62-216l-145 0c-30 0-55 15-72 43-12 20-16 40-16 41l-3 15 267 0 0-12c0 0 1-15 9-29 10-18 26-26 49-26 13 0 22 4 29 11 16 19 16 55 14 67l0 1 0 207-215 0c-7 0-12 5-12 12 0 8 5 13 12 13l241 0 0-231c1-7 5-57-21-86-12-13-28-20-48-20-41 0-62 22-72 40-5 10-8 20-10 27l-210 0c2-5 5-11 8-17 13-20 29-29 50-29l145 0c7 0 13-6 13-14 0-7-6-13-13-13z m95 259l-139 0c-8 0-14 6-14 14 0 8 6 14 14 14l139 0c7 0 14-6 14-14 0-8-7-14-14-14z m0-58l-103 0c-7 0-14 6-14 14 0 8 7 14 14 14l103 0c7 0 14-6 14-14 0-8-7-14-14-14z m0-58l-111 0c-7 0-14 6-14 14 0 8 7 14 14 14l111 0c7 0 14-6 14-14 0-8-7-14-14-14z"/>
<glyph glyph-name="script-run" unicode="&#80;" d="M209 309l-79 60c-5 3-11 4-16 1-4-2-8-7-8-13l0-126c0-6 4-11 9-14 2-1 4-1 6-1 3 0 7 1 9 3l80 68c3 3 5 7 5 11 0 5-2 9-6 11m89-232l-145 0c-30 0-55 16-72 44-12 20-16 40-16 41l-3 16 267 0 0-13c0 0 1-14 9-29 10-17 26-26 49-26 13 0 22 4 29 12 16 18 16 54 14 66l0 1 0 206-269 0c-7 0-13 6-13 13 0 7 6 13 13 13l295 0 0-230c1-8 5-57-21-86-12-14-28-21-48-21-41 0-62 23-72 41-5 10-8 19-10 28l-210 0c2-7 5-12 8-18 13-20 29-31 50-31l145 0c7 0 13-7 13-14 0-7-6-13-13-13z m95 261l-180 0c-7 0-14 6-14 14 0 8 7 14 14 14l180 0c7 0 14-6 14-14 0-8-7-14-14-14z m0-59l-135 0c-8 0-14 7-14 15 0 7 6 14 14 14l135 0c7 0 14-7 14-14 0-8-7-15-14-15z m0-58l-180 0c-7 0-14 7-14 14 0 8 7 15 14 15l180 0c7 0 14-7 14-15 0-8-7-14-14-14z"/>
<glyph glyph-name="script-new" unicode="&#81;" d="M298 80l-145 0c-30 0-55 15-72 43-12 20-16 40-16 41l-3 16 267 0 0-12c0-1 1-15 9-29 10-18 26-27 49-27 13 0 22 4 29 12 16 18 16 54 14 66l0 1 0 206-269 0c-7 0-13 6-13 13 0 7 6 13 13 13l295 0 0-230c1-8 5-57-21-86-12-14-28-21-48-21-41 0-62 23-72 41-5 10-8 19-10 28l-210 0c2-7 5-12 8-18 13-20 29-30 50-30l145 0c7 0 13-7 13-14 0-7-6-13-13-13z m95 260l-180 0c-7 0-14 6-14 14 0 8 7 14 14 14l180 0c7 0 14-6 14-14 0-8-7-14-14-14z m0-59l-135 0c-8 0-14 7-14 15 0 7 6 14 14 14l135 0c7 0 14-7 14-14 0-8-7-15-14-15z m0-58l-180 0c-7 0-14 7-14 14 0 8 7 15 14 15l180 0c7 0 14-7 14-15 0-7-7-14-14-14z m-250 90l0 53c0 9-7 16-16 16-9 0-16-7-16-16l0-53-54 0c-9 0-16-8-16-17 0-9 8-17 16-17l54 0 0-53c0-9 7-16 16-16 9 0 16 8 16 17l0 52 54 0c9 0 16 8 16 17 0 9-8 16-17 16z"/>
<glyph glyph-name="hifi-forum" unicode="&#50;" d="M265 410c-83 0-150-68-150-150 0-24 6-47 16-67l-27-79 80 20c23-16 51-25 81-25 83 0 150 68 150 150 0 83-67 151-150 151z m38-248c-9 0-17 7-17 17 0 7 4 14 12 16l0 46-74 33 0-56c7-2 12-8 12-16 0-10-7-18-17-18-10 0-19 8-19 18 0 7 6 13 10 16l0 111c-4 2-10 9-10 16 0 10 9 17 19 17 9 0 17-7 17-17 0-8-5-14-12-16l0-41 74-33 0 51c-8 3-12 9-12 16 0 10 7 18 17 18 10 0 17-8 17-18 0-7-5-14-12-16l0-110c7-3 12-9 12-17 0-10-7-17-17-17z"/>
<glyph glyph-name="hifi-logo-small" unicode="&#83;" d="M374 374c-32 32-74 49-119 49-46 0-88-17-120-49-32-32-49-74-49-119 0-45 17-87 49-118 32-32 74-49 119-49 45 0 88 17 119 49 32 32 49 73 49 118 1 45-17 87-48 119z m-17-221c-28-28-65-43-103-43-39 0-75 15-103 43-27 27-42 64-42 102 0 39 15 75 42 103 28 28 64 43 103 43 38 0 75-15 103-43 27-28 42-64 42-103 0-39-15-76-42-102z m-145 47c-5 0-9 3-9 6l0 126c0 3 4 7 9 7 6 0 10-4 10-7l0-125c0-4-4-7-10-7z m0 118c-5 0-10 2-14 6-7 7-7 20 0 27 5 5 9 6 14 6 6 0 11-2 14-6 4-4 5-8 5-14 0-5-2-10-5-13-4-4-8-6-14-6z m0-144c-5 0-10 2-14 5-4 5-5 9-5 14 0 6 2 11 5 14 5 4 9 5 14 5 6 0 11-2 14-5 4-4 5-8 5-14 0-5-2-10-5-14-4-3-8-5-14-5z m85 2c-6 0-10 3-10 7l0 121c0 4 4 7 10 7 5 0 9-3 9-7l0-121c0-5-4-7-9-7z m0 120c-6 0-11 2-14 5-8 8-8 20 0 28 4 4 8 5 14 5 5 0 10-2 14-5 4-4 5-9 5-14 0-5-2-11-5-14-4-3-9-5-14-5z m0-144c-6 0-11 2-14 5-8 7-8 20 0 28 4 4 8 5 14 5 5 0 10-2 14-5 4-5 5-9 5-14 0-5-2-11-5-14-4-3-9-5-14-5z m1 73l-85 40 1 18 86-40z"/>
<glyph glyph-name="avatar-1" unicode="&#84;" d="M293 71c-1 0-1 0-1 0-14-1-14-1-16 12-6 43-11 86-16 128-1 2-1 4-1 6-3 0-6 0-9 0-2-10-3-20-4-31-4-36-8-72-12-109-1-7-2-8-9-8-2 0-5 0-7 0 0 74 1 181 1 254-1-1-33 0-44 1-15 0-62 1-79 1-8 0-14 3-18 10-1 2-2 4-4 7 8 0 15 0 22 1 35 1 99 8 100 12 14 11 23 10 36 10 15 0 31 0 46-1 11 0 24 3 37-10 20-10 81-10 123-11 0 0 1 0 1 0-4-12-12-17-25-17-29-1-77-3-127-1 2-73 4-181 6-254z m-32 371c16-6 14-20 13-32 0-5-1-10-1-14-2-11-10-18-20-18-11 0-19 8-20 18-1 6-1 13-2 20-1 7 3 13 11 15 10 3 10 3 19 11z"/>
<glyph glyph-name="placemark" unicode="&#85;" d="M134 98c31-32 73-49 119-49l1 0c45 0 86 16 117 47 31 30 48 71 48 114 1 46-16 88-46 120-9 9-20 17-31 24-3-7-6-15-10-22 5-4 11-8 16-13 15-14 28-32 33-46l1-1c0-1 0-2-1-3l-1 0c-12-7-25-10-38-12-2-1-4-1-7-2l-1 0c0 0-1 0-2 1 0 0-1 1-1 1l0 1c-3 15-6 31-12 46l-7-15c4-11 6-21 8-32l0-1c0-1 0-1 0-2-1-1-1-1-2-1l-1 0-55-3-4 0c0 0-1 0-1 0-1 1-1 2-1 2l0 74c-5 9-8 17-11 26 0-1 0-1 0-1l-1 0 0-97c0 0 0-1 0-2 0-1-1-2-2-2l-1 0c0 0-1 0-1 0l-51 3c-1 0-2 0-2 1-1 1-1 2 0 2l0 1c5 27 13 58 35 83 6 6 12 13 20 16 0 0 1 0 1 0-3 7-6 15-9 22-37-4-71-20-99-47-30-30-47-70-48-113-1-46 16-88 47-120z m198 72c0 4 1 7 1 10l0 0c0 7 1 14 1 21 0 9 0 18 0 27 0 3 0 6 0 9l0 1c-1 1-1 6 5 7 11 3 23 5 34 8l1 0c3 1 7 2 11 2l1 1c1 0 3-1 3-2l0-1c0-1 1-1 1-2 1-3 1-5 2-8 3-14 4-27 3-40-2-12-8-21-18-25-8-3-17-5-25-8-3 0-7-1-10-2-1-1-3-1-4-1-1 0-1 0-2 0l-1-1c0 0 0 0 0 0-1 0-2 1-2 1-1 1-1 1-1 2z m2-17c12 4 23 7 35 10l14 4c1 0 2 0 2-1 1 0 1-2 1-2l-1-2c-7-18-17-34-30-47-13-13-28-24-46-31l-2-1c0 0-1 0-1 0-1 0-1 0-2 0 0 1-1 2-1 3l1 1c0 1 0 1 0 1 0 1 0 2 1 3 6 9 15 24 22 55 1 3 2 6 7 7z m-78 83c0 1 1 3 2 3l3 0 41 2 13 1c0 0 1 0 1 0l1 0c0 0 0-1 1-1l1 0c1 0 2-1 2-3 0-1 0-3 0-5 0-2 0-4 0-6l0-5c1-7 1-15 0-22l0-4c0-5 0-11-1-17 0-1 0-3 0-5 0-1 0-3 0-4 0-2-1-7-7-8-10 0-20-1-30-2l-2 0c-3-1-8-1-12-1-5-1-7-1-8-1-1 0-1 0-2 0l-2 1c-1 1-1 2-1 3l0 74z m0-92c0 1 1 3 2 3l3 0 51 3c1 0 2 0 2-1 1 0 1-1 1-2l0-1c0 0 0-1 0-1 0 0 0 0-1-1 0 0 0-1 0-1l0-1c0-1-1-3-1-4-1-2-2-5-3-7l-1-3c-3-9-7-18-11-27-4-8-9-14-14-19-5-6-13-9-23-10l-3 0c-1 0-1 0-2 1 0 0 0 1 0 2l0 69z m-52-59c-1-1-1-2-2-2 0 0-1 1-1 1l-2 1c-27 6-64 40-78 79l-1 1c0 1 0 2 1 3 1 1 2 1 3 1l13-4c12-4 24-7 36-11 3-1 6-2 7-7 5-24 12-42 23-57l1-2c1-1 1-3 0-3z m40-10c0-1-1-1-1-2-1 0-2 0-2 0 0 0-1 0-1 0-1 0-14 4-20 11-15 19-23 41-29 61 0 0 0 0 0 1l0 1c0 1 0 1 1 2 0 1 1 1 2 1l1 0 29-2 17-1c2-1 3-2 3-3l0-69z m-21 165l18-2c2 0 3-1 3-2l0-75c0-1-1-2-1-2-1-1-2-1-2-1 0 0 0 0-1 0 0 0-14 2-21 2-8 1-17 2-26 3-4 0-6 4-6 7-2 18-3 35-3 47 0 5-1 22-1 22 0 1 1 2 1 2 1 1 1 1 2 1z m-98 32l1 1c12 31 42 59 76 71l3 1c1 0 2 0 3-1 0-1 0-2 0-3l-2-3c-18-25-25-53-31-79l0-1c0-1 0-2-1-2 0 0-1-1-1-1-1 0-1 0-1 0l-1 1c-5 1-11 3-17 4-8 2-18 4-27 8l0 1c-2 0-2 2-2 3z m-8-18l1 1c0 1 1 2 3 2l49-13c1 0 2-1 2-3l2-71c0-1 0-2-1-2-1-1-1-1-2-1 0 0 0 0 0 0l-1 0c-1 0-2 1-3 1l0 0c-2 0-4 1-7 1-16 4-29 9-39 17-6 4-9 8-9 15-1 19 1 37 5 53z m178 42c-8 16-14 31-21 46-8 17-17 35-24 52-14 31 3 65 36 71 28 5 56-16 59-44 0-9-1-18-5-26-14-32-29-64-44-97 0 0-1-1-1-2z m27 117c0 15-12 28-27 28-15 0-27-12-28-27 0-16 13-28 28-28 15 0 27 12 27 27z"/>
<glyph glyph-name="box" unicode="&#86;" d="M318 74l126 89 15-22-126-88z m-137 101l0-99 27 0 0 96z m145-125c-1 0-1 0-2 0l-262 26c-7 1-12 7-12 13l0 263c0 4 1 7 4 10 3 2 7 4 10 3l263-26c7 0 12-6 12-13l0-262c0-4-2-8-4-10-3-3-6-4-9-4z m-250 51l236-23 0 236-236 23z m377 326l-263 26c-3 1-7-1-10-3-3-3-4-6-4-10l0-21c3 2 7 3 11 3 0 0 0 0 1 0 3 2 7 4 11 4 2 0 3 0 5-1l234-23 0-236c0-7-3-10-10-14 1-2 1-5 1-7 1-2 0-3 0-5l21-2c1 0 1 0 2 0 3 0 6 1 8 4 3 2 5 6 5 10l0 262c0 7-5 13-12 13z m-397-64l125 88 16-22-126-88z m262-26l126 88 15-22-126-88z m-256-123l2 27 263-26-3-26z m146 37l0 91-27 0 0-88c9-1 18-2 27-3z"/>
<glyph glyph-name="community" unicode="&#48;" d="M50 175c-4 0-8 2-11 6-4 6-2 14 4 18l24 16 69 48 89-64c6-4 7-13 3-19-5-6-13-7-19-3l-74 53-53-37-24-16c-3-1-5-2-8-2z m130-10l-44 32-47-32-22-14 0-63 135 0 0 60z m120 10c-4 0-9 2-11 6-4 6-3 14 3 18l25 16 68 48 89-64c6-4 7-13 3-19-4-6-13-7-19-3l-73 53-54-37-24-16c-2-1-5-2-7-2z m129-10l-46 32-45-32-22-14 0-63 135 0 0 60z m-256 202c-4 0-9 2-11 5-4 7-3 15 4 19l24 16 68 48 89-65c6-4 7-12 3-18-4-6-12-7-18-3l-74 53-53-37-25-16c-2-2-5-2-7-2z m129-11l-46 32-45-31-22-15 0-62 135 0 0 60z"/>
<glyph glyph-name="grab-handle" unicode="&#88;" d="M280 318c0 10 9 19 20 19 10 0 19-9 19-19 0-11-9-20-19-20-11 0-20 9-20 20z m-46-20c-5 0-10 2-14 6-3 4-5 8-5 14 0 5 2 10 5 13 4 4 9 6 14 6 5 0 10-2 13-6 4-3 6-8 6-13 0-5-2-10-6-14-3-4-8-6-13-6z m46-42c0 10 9 19 20 19 10 0 19-9 19-19 0-11-9-20-19-20-11 0-20 9-20 20z m-46-20c-5 0-10 3-14 6-3 4-5 9-5 14 0 5 2 10 5 13 4 4 9 6 14 6 5 0 10-2 13-6 4-3 6-8 6-13 0-5-2-10-6-14-3-3-8-6-13-6z m46-42c0 10 9 19 20 19 10 0 19-9 19-19 0-11-9-19-19-19-11 0-20 8-20 19z m-46-19c-5 0-10 2-14 5-3 4-5 9-5 14 0 5 2 10 5 13 4 4 9 6 14 6 5 0 10-2 13-6 4-3 6-8 6-13 0-5-2-10-6-14-3-3-8-5-13-5z"/>
<glyph glyph-name="search" unicode="&#89;" d="M277 185c-48 0-88 40-88 88 0 49 40 89 88 89 49 0 89-40 89-89 0-48-40-88-89-88z m0 159c-38 0-70-32-70-71 0-38 32-70 70-70 39 0 71 32 71 70 0 39-32 71-71 71z m-112-205c-2 0-4 1-6 2-4 4-4 9 0 13l61 64c3 4 9 4 12 0 4-3 4-9 1-12l-61-64c-2-2-5-3-7-3z"/>
<glyph glyph-name="disclosure-collapse" unicode="&#90;" d="M264 198l-60 59c-4 5-4 12 0 17 5 4 13 4 17 0l43-43 43 44c4 4 12 4 17 0 4-5 4-13 0-17z"/>
<glyph glyph-name="script-upload" unicode="&#82;" d="M260 91l-117 0c-29 0-54 15-71 43-13 20-17 40-17 41l-3 16 157 0c7 0 13-5 13-12 0-7-6-12-13-12l-124 0c2-8 5-12 9-18 12-20 29-31 49-31l117 0c7 0 13-6 13-14 0-7-6-13-13-13z m117 7c-7 0-12 6-12 13 0 7 5 13 12 13 13 0 23 4 30 11 16 19 16 55 14 67l1 1 0 0 0 206-257 0 0-226c0-7-5-13-12-13-8 0-13 6-13 13l0 251 306 0 0-230c1-7 5-57-20-86-13-13-28-20-49-20z m6 253l-179 0c-8 0-14 6-14 14 0 8 6 14 14 14l179 0c8 0 14-6 14-14 0-8-6-14-14-14z m0-58l-179 0c-8 0-14 6-14 14 0 8 6 14 14 14l179 0c8 0 14-6 14-14 0-8-6-14-14-14z m0-58l-16 0c-8 0-14 6-14 14 0 8 6 14 14 14l16 0c8 0 14-6 14-14 0-8-6-14-14-14z m-110 0l-69 0c-8 0-14 6-14 14 0 8 6 14 14 14l69 0c8 0 14-6 14-14 0-8-6-14-14-14z m127-71l-80 83-81-83 49 0 0-136 62 22 0 114z"/>
<glyph glyph-name="code" unicode="&#87;" d="M92 242l74 73c6 6 15 6 21 0 6-5 6-15 0-21l-53-52 54-54c6-6 6-16 0-21-5-6-15-6-21 0z m347 0l-74 73c-5 6-15 6-21 0-5-5-5-15 0-21l53-52-54-54c-6-6-6-16 0-21 6-6 15-6 21 0z m-223-137c-1 0-2 0-4 1-5 2-8 8-6 14l98 254c3 6 9 8 15 6 5-2 8-8 6-14l-99-254c-1-4-5-7-10-7z"/>
<glyph glyph-name="avatar" unicode="&#60;" d="M256 88c-93 0-169 75-169 168 0 93 76 169 169 169 93 0 169-76 169-169 0-93-76-168-169-168z m0 316c-81 0-148-66-148-148 0-81 67-147 148-147 81 0 148 66 148 147 0 82-67 148-148 148z m97-90l-1 1c-3 3-7 4-10 4-1 0-61-9-86-9-1 0-1 0-2 0-25 0-87 10-87 10-5 0-10-2-13-6l-1-2c-2-3-2-7-1-10 1-4 3-6 6-8 12-5 49-20 60-22 2 0 5 0 6-7 1-8-3-46-7-65-5-17-13-40-13-41-2-6 1-13 7-15l8-3c3-1 6-1 9 1 3 1 5 4 6 7l21 65 20-67c1-3 3-6 6-7 2-1 4-1 5-1 2 0 3 0 5 0l7 3c5 2 8 8 7 14 0 0-6 24-11 44-3 12-4 30-5 45 0 9-1 16-2 22 0 1 0 4 5 5 0 0 1 0 2 0l55 22c4 2 6 5 7 9 1 4 0 8-3 11z m-68 37c0-16-13-29-29-29-16 0-29 13-29 29 0 16 13 29 29 29 16 0 29-13 29-29z"/>
<glyph glyph-name="arrows-h" unicode="&#58;" d="M512 256c0-5-2-9-5-13l-74-73c-3-4-7-5-12-5-5 0-10 1-13 5-4 4-6 8-6 13l0 36-292 0 0-36c0-5-2-9-6-13-3-4-8-5-13-5-5 0-9 1-12 5l-74 73c-3 4-5 8-5 13 0 5 2 9 5 13l74 73c3 4 7 5 12 5 5 0 10-1 13-5 4-4 6-8 6-13l0-36 292 0 0 36c0 5 2 9 6 13 3 4 8 5 13 5 5 0 9-1 12-5l74-73c3-4 5-8 5-13z"/>
<glyph glyph-name="arrows-v" unicode="&#59;" d="M347 421c0-5-1-10-5-13-4-4-8-6-13-6l-36 0 0-292 36 0c5 0 9-2 13-6 4-3 5-8 5-13 0-5-1-9-5-12l-73-74c-4-3-8-5-13-5-5 0-9 2-13 5l-73 74c-4 3-5 7-5 12 0 5 1 10 5 13 4 4 8 6 13 6l36 0 0 292-36 0c-5 0-9 2-13 6-4 3-5 8-5 13 0 5 1 9 5 12l73 74c4 3 8 5 13 5 5 0 9-2 13-5l73-74c4-3 5-7 5-12z"/>
<glyph glyph-name="arrows" unicode="&#96;" d="M512 256c0-5-2-9-5-13l-74-73c-3-4-7-5-12-5-5 0-10 1-13 5-4 4-6 8-6 13l0 36-109 0 0-109 36 0c5 0 9-2 13-6 4-3 5-8 5-13 0-5-1-9-5-12l-73-74c-4-3-8-5-13-5-5 0-9 2-13 5l-73 74c-4 3-5 7-5 12 0 5 1 10 5 13 4 4 8 6 13 6l36 0 0 109-109 0 0-36c0-5-2-9-6-13-3-4-8-5-13-5-5 0-9 1-12 5l-74 73c-3 4-5 8-5 13 0 5 2 9 5 13l74 73c3 4 7 5 12 5 5 0 10-1 13-5 4-4 6-8 6-13l0-36 109 0 0 109-36 0c-5 0-9 2-13 6-4 3-5 8-5 13 0 5 1 9 5 12l73 74c4 3 8 5 13 5 5 0 9-2 13-5l73-74c4-3 5-7 5-12 0-5-1-10-5-13-4-4-8-6-13-6l-36 0 0-109 109 0 0 36c0 5 2 9 6 13 3 4 8 5 13 5 5 0 9-1 12-5l74-73c3-4 5-8 5-13z"/>
<glyph glyph-name="compress" unicode="&#33;" d="M256 238l0-128c0-5-2-10-5-13-4-4-8-6-13-6-5 0-10 2-13 6l-41 41-95-95c-2-2-4-3-7-3-2 0-4 1-6 3l-33 33c-2 2-3 4-3 6 0 3 1 5 3 7l95 95-41 41c-4 3-6 8-6 13 0 5 2 9 6 13 3 3 8 5 13 5l128 0c5 0 9-2 13-5 3-4 5-8 5-13z m216 192c0-3-1-5-3-7l-95-95 41-41c4-3 6-8 6-13 0-5-2-9-6-13-3-3-8-5-13-5l-128 0c-5 0-9 2-13 5-3 4-5 8-5 13l0 128c0 5 2 10 5 13 4 4 8 6 13 6 5 0 10-2 13-6l41-41 95 95c2 2 4 3 7 3 2 0 4-1 6-3l33-33c2-2 3-4 3-6z"/>
<glyph glyph-name="expand" unicode="&#34;" d="M252 210c0-2-1-4-3-6l-94-95 41-41c3-4 5-8 5-13 0-5-2-9-5-13-4-4-8-5-13-5l-128 0c-5 0-9 1-13 5-4 4-5 8-5 13l0 128c0 5 1 9 5 13 4 3 8 5 13 5 5 0 9-2 13-5l41-41 95 94c2 2 4 3 6 3 3 0 5-1 7-3l32-32c2-2 3-4 3-7z m223 247l0-128c0-5-1-9-5-13-4-3-8-5-13-5-5 0-9 2-13 5l-41 41-95-94c-2-2-4-3-6-3-3 0-5 1-7 3l-32 32c-2 2-3 4-3 7 0 2 1 4 3 6l94 95-41 41c-3 4-5 8-5 13 0 5 2 9 5 13 4 4 8 5 13 5l128 0c5 0 9-1 13-5 4-4 5-8 5-13z"/>
<glyph glyph-name="placemark-1" unicode="&#35;" d="M475 213l0 176c-32-17-61-26-87-26-16 0-29 3-41 10-19 9-37 16-53 21-16 6-33 8-51 8-33 0-71-12-115-36l0-171c47 21 88 32 124 32 10 0 20-1 29-2 10-1 19-4 28-7 10-4 17-7 22-9 6-3 13-6 24-12l8-4c8-4 18-6 29-6 23 0 50 9 83 26z m-384 226c0-7-1-13-5-18-3-6-7-10-13-14l0-361c0-3-1-5-2-7-2-2-4-2-7-2l-18 0c-3 0-5 0-7 2-2 2-2 4-2 7l0 361c-6 4-10 8-14 14-3 5-5 11-5 18 0 10 4 19 11 26 7 7 16 10 26 10 10 0 19-3 26-10 7-7 10-16 10-26z m421-18l0-218c0-8-3-13-10-17-2-1-4-2-5-2-41-22-77-33-105-33-17 0-32 3-45 10l-8 4c-13 6-22 10-29 13-6 3-15 6-26 9-10 2-21 4-32 4-20 0-42-5-68-13-25-8-47-18-65-29-3-2-6-3-9-3-3 0-6 1-9 3-7 3-10 9-10 16l0 212c0 6 3 11 9 15 7 4 14 8 23 12 8 5 19 9 32 15 14 6 28 11 44 14 15 4 30 6 44 6 21 0 41-3 60-9 18-6 38-14 60-25 7-3 15-5 25-5 23 0 53 11 89 32 4 2 7 4 8 5 6 3 12 3 18-1 6-4 9-9 9-15z"/>
<glyph glyph-name="circle" unicode="&#36;" d="M366 238c0 35-13 65-38 90-25 25-55 38-90 38-36 0-66-13-91-38-25-25-37-55-37-90 0-36 12-66 37-91 25-25 55-37 91-37 35 0 65 12 90 37 25 25 38 55 38 91z m36 0c0-23-4-44-13-64-8-20-20-38-35-53-15-14-32-26-52-35-21-9-42-13-64-13-23 0-44 4-64 13-20 9-38 21-53 35-14 15-26 33-35 53-9 20-13 41-13 64 0 22 4 43 13 64 9 20 21 37 35 52 15 15 33 27 53 35 20 9 41 13 64 13 22 0 43-4 64-13 20-8 37-20 52-35 15-15 27-32 35-52 9-21 13-42 13-64z"/>
<glyph glyph-name="hand-pointer" unicode="&#57;" d="M183 475c-10 0-19-3-26-10-7-7-11-16-11-26l0-256-43 58c-8 10-18 15-30 15-10 0-19-4-26-11-7-7-10-16-10-26 0-8 2-15 7-22l110-146c7-10 17-14 29-14l205 0c4 0 8 1 11 3 4 3 6 6 7 10l26 105c5 19 7 37 7 56l0 62c0 8-3 14-8 20-5 6-12 9-20 9-7 0-14-3-19-8-5-6-8-12-8-20l-9 0 0 18c0 9-3 17-9 23-6 6-14 10-23 10-9 0-16-4-23-10-6-6-9-14-9-22l0-19-9 0 0 26c0 10-4 19-11 27-7 8-16 11-26 11-10 0-19-3-26-10-7-8-10-16-10-26l0-28-10 0 0 163c0 11-3 20-10 27-7 8-16 11-26 11z m0 37c20 0 38-7 52-22 14-15 21-32 21-53l0-63c4 1 7 1 9 1 19 0 35-7 50-20 9 4 18 6 28 6 21 0 39-8 52-25 6 2 11 2 16 2 18 0 33-6 46-19 12-13 18-28 18-46l0-62c0-22-2-44-8-64l-26-106c-3-12-9-22-19-29-10-8-21-12-34-12l-205 0c-12 0-22 3-33 8-10 5-19 12-26 21l-109 146c-10 13-15 28-15 44 0 20 7 38 21 52 14 14 32 22 52 22 13 0 25-4 37-10l0 156c0 20 7 37 21 52 14 14 32 21 52 21z m36-402l0 109-9 0 0-109z m74 0l0 109-10 0 0-109z m73 0l0 109-9 0 0-109z"/>
<glyph glyph-name="plus-square-o" unicode="&#37;" d="M384 283l0-18c0-3-1-5-3-6-1-2-3-3-6-3l-101 0 0-101c0-2-1-4-2-6-2-2-4-3-7-3l-18 0c-3 0-5 1-7 3-1 2-2 4-2 6l0 101-101 0c-3 0-5 1-6 3-2 1-3 3-3 6l0 18c0 3 1 5 3 7 1 2 3 3 6 3l101 0 0 100c0 3 1 5 2 7 2 1 4 2 7 2l18 0c3 0 5-1 7-2 1-2 2-4 2-7l0-100 101 0c3 0 5-1 6-3 2-2 3-4 3-7z m37-128l0 238c0 13-5 23-14 32-9 9-20 14-32 14l-238 0c-12 0-23-5-32-14-9-9-14-19-14-32l0-238c0-12 5-23 14-32 9-9 20-13 32-13l238 0c12 0 23 4 32 13 9 9 14 20 14 32z m36 238l0-238c0-22-8-42-24-58-16-16-35-24-58-24l-238 0c-23 0-42 8-58 24-16 16-24 36-24 58l0 238c0 23 8 42 24 58 16 16 35 24 58 24l238 0c23 0 42-8 58-24 16-16 24-35 24-58z"/>
<glyph glyph-name="square" unicode="&#39;" d="M375 439l-238 0c-12 0-23-5-32-14-9-9-14-19-14-32l0-238c0-12 5-23 14-32 9-9 20-13 32-13l238 0c12 0 23 4 32 13 9 9 14 20 14 32l0 238c0 13-5 23-14 32-9 9-20 14-32 14z m82-46l0-238c0-22-8-42-24-58-16-16-35-24-58-24l-238 0c-23 0-42 8-58 24-16 16-24 36-24 58l0 238c0 23 8 42 24 58 16 16 35 24 58 24l238 0c23 0 42-8 58-24 16-16 24-35 24-58z"/>
<glyph glyph-name="align-center" unicode="&#56;" d="M416 434l-320 0c-10 0-17-7-17-17l0-16c0-10 7-17 17-17l320 0c10 0 17 7 17 17l0 16c0 10-7 17-17 17z m-26-135l0 16c0 10-8 17-17 17l-234 0c-9 0-17-7-17-17l0-16c0-10 8-17 17-17l234 0c9 0 17 7 17 17z m8-171l-284 0c-10 0-18-7-18-17l0-16c0-10 8-17 18-17l284 0c10 0 18 7 18 17l0 0 0 16 0 0c0 10-8 17-18 17z m-25 85c0 10-8 17-18 17l-198 0c-10 0-18-7-18-17l0-16c0-10 8-17 18-17l198 0c10 0 18 7 18 17 0 0 0 0 0 0z"/>
<glyph glyph-name="align-justify" unicode="&#41;" d="M416 433l-320 0c-10 0-17-8-17-17l0-16c0-10 7-18 17-18l320 0c10 0 17 8 17 18l0 16c0 9-7 17-17 17z m0-101l-320 0c-10 0-17-8-17-17l0-16c0-10 7-18 17-18l320 0c10 0 17 8 17 18l0 16c0 9-7 17-17 17z m0-101l-320 0c-10 0-17-8-17-18l0-16c0-9 7-17 17-17l320 0c10 0 17 8 17 17l0 16c0 10-7 18-17 18z m0-101l-320 0c-10 0-17-8-17-18l0-16c0-9 7-17 17-17l320 0c10 0 17 8 17 17l0 16c0 10-7 18-17 18z"/>
<glyph glyph-name="align-left" unicode="&#42;" d="M416 434l-320 0c-10 0-17-7-17-17l0-16c0-10 7-17 17-17l320 0c10 0 17 7 17 17l0 16c0 10-7 17-17 17z m-320-152l234 0c9 0 17 7 17 17l0 16c0 10-8 18-17 18l-234 0c-10 0-17-8-17-18l0-16c0-10 7-17 17-17z m285-154l-285 0c-10 0-17-7-17-17l0-16c0-10 7-17 17-17l285 0c10 0 17 7 17 17l0 0 0 16 0 0c0 10-7 17-17 17z m-285 52l199 0c9 0 17 7 17 17 0 0 0 0 0 0l0 16c0 10-8 17-17 17l-199 0c-10 0-17-7-17-17l0-16c0-10 7-17 17-17z"/>
<glyph glyph-name="align-right" unicode="&#94;" d="M416 434l-320 0c-10 0-17-7-17-17l0-16c0-10 7-17 17-17l320 0c10 0 17 7 17 17l0 16c0 10-7 17-17 17z m0-102l-234 0c-9 0-17-7-17-17l0-16c0-10 8-17 17-17l234 0c10 0 17 7 17 17l0 16c0 10-7 17-17 17z m0-204l-285 0c-10 0-17-7-17-17l0-16c0-10 7-17 17-17l285 0c9 0 17 7 17 17l0 0 0 16 0 0c0 10-8 17-17 17z m17 85c0 10-7 17-17 17l-199 0c-9 0-17-7-17-17l0-16c0-10 8-17 17-17l199 0c10 0 17 7 17 17 0 0 0 0 0 0z"/>
<glyph glyph-name="bars" unicode="&#55;" d="M475 128l0-37c0-5-1-9-5-12-4-4-8-6-13-6l-402 0c-5 0-9 2-13 6-4 3-5 7-5 12l0 37c0 5 1 9 5 13 4 3 8 5 13 5l402 0c5 0 9-2 13-5 4-4 5-8 5-13z m0 146l0-36c0-5-1-10-5-13-4-4-8-6-13-6l-402 0c-5 0-9 2-13 6-4 3-5 8-5 13l0 36c0 5 1 10 5 13 4 4 8 6 13 6l402 0c5 0 9-2 13-6 4-3 5-8 5-13z m0 147l0-37c0-5-1-9-5-13-4-3-8-5-13-5l-402 0c-5 0-9 2-13 5-4 4-5 8-5 13l0 37c0 5 1 9 5 12 4 4 8 6 13 6l402 0c5 0 9-2 13-6 4-3 5-7 5-12z"/>
<glyph glyph-name="circle-slash" unicode="&#44;" d="M256 416c-88 0-160-72-160-160 0-88 72-160 160-160 88 0 160 72 160 160 0 88-72 160-160 160z m0-64c14 0 27-3 39-8l-127-127c-5 12-8 25-8 39 0 53 43 96 96 96z m0-192c-14 0-27 3-39 9l127 126c5-12 8-25 8-39 0-53-43-96-96-96z"/>
<glyph glyph-name="sync" unicode="&#40;" d="M392 275c6-41-7-84-39-115-47-47-119-52-173-17l38 36-138 19 19-134 42 40c76-55 183-50 251 18 40 39 58 91 56 142z m-233 77c47 46 119 52 173 17l-38-36 138-19-19 134-42-40c-76 55-183 50-251-18-40-39-58-91-56-142l56-11c-6 41 7 84 39 115z"/>
<glyph glyph-name="key" unicode="&#45;" d="M479 282c-1 2-1 4-3 6-2 2-5 3-7 3l-202 0c-13 51-60 89-115 89-65 0-119-54-119-119 0-66 54-119 119-119 59 0 107 43 117 99l45 0 0-65 0 0c0-5 5-9 10-9 0 0 0 0 0 0l0 0 31 0 0 0c5 0 10 4 10 9l0 0 0 65 26 0 0-100 0 0c0-5 4-9 9-9 0 0 0 0 0 0l32 0c0 0 0 0 0 0 5 0 9 4 10 9l0 0 0 100 27 0 0 0c2 0 5 0 7 2 2 2 3 5 3 7l0 0 0 32 0 0z m-327-90c-37 0-68 30-68 68 0 38 31 68 68 68 38 0 68-30 68-68 0-38-30-68-68-68z"/>
<glyph glyph-name="link" unicode="&#46;" d="M202 136c5 5 10 7 17 7 7 0 13-2 19-7 10-11 10-23 0-36 0 0-22-20-22-20-19-19-42-29-68-29-26 0-49 10-68 29-19 19-29 42-29 67 0 27 10 50 29 69 0 0 76 76 76 76 24 23 48 36 73 39 26 3 47-4 66-22 5-5 8-11 8-18 0-7-3-13-8-19-12-11-24-11-36 0-17 17-40 11-68-17 0 0-75-75-75-75-9-9-14-20-14-33 0-13 5-23 14-31 9-9 19-14 32-14 13 0 23 5 32 14 0 0 22 20 22 20m230 294c19-19 29-42 29-68 0-26-10-49-29-68 0 0-81-81-81-81-25-25-51-37-77-37-21 0-40 9-57 26-5 5-7 10-7 17 0 7 2 13 7 19 5 4 11 7 18 7 7 0 13-3 18-7 17-17 38-13 62 12 0 0 81 80 81 80 10 9 15 20 15 32 0 13-5 24-15 32-8 9-17 14-28 16-11 2-22-2-31-11 0 0-26-25-26-25-5-5-11-7-18-7-7 0-13 2-18 7-11 11-11 23 0 36 0 0 26 25 26 25 18 19 40 27 65 26 25-1 47-11 66-31"/>
<glyph glyph-name="location" unicode="&#47;" d="M256 512c-88 0-160-72-160-160 0-89 80-208 160-352 80 144 160 263 160 352 0 88-71 160-160 160z m0-224c-35 0-64 28-64 64 0 35 29 64 64 64 36 0 64-29 64-64 0-36-28-64-64-64z"/>
<glyph glyph-name="carat-r" unicode="&#51;" d="M304 249l-55-43 0 86z"/>
<glyph glyph-name="carat-l" unicode="&#52;" d="M225 250l55 43 0-87z"/>
<glyph glyph-name="folder-lg" unicode="&#62;" d="M203 385l38-51 185 0 0-206-333 0 0 257 110 0m0 30l-110 0c-17 0-30-14-30-30l0-257c0-17 13-30 30-30l333 0c17 0 30 13 30 30l0 206c0 16-13 30-30 30l-170 0-29 39c-6 7-15 12-24 12z"/>
<glyph glyph-name="folder-sm" unicode="&#63;" d="M226 324l20-27 98 0 0-109-176 0 0 136 58 0m0 24l-58 0c-13 0-24-11-24-24l0-136c0-13 11-24 24-24l176 0c13 0 24 11 24 24l0 109c0 13-11 24-24 24l-86 0-12 17c-5 6-12 10-20 10z"/>
<glyph glyph-name="level-up" unicode="&#49;" d="M331 274c-9 9-21 14-37 14l-68 0 32 32c5 4 5 12 0 17-2 2-5 3-8 3-3 0-6-1-9-3l-59-60 60-60c5-4 13-4 17 0 5 5 5 12 0 17l-31 31 66 0c9 0 16-2 19-7 6-6 5-15 5-16l0 0 0-75c0-6 5-12 12-12 6 0 11 6 11 12l0 73c0 5 1 21-10 34z"/>
<glyph glyph-name="info" unicode="&#91;" d="M267 218c-1-7 0-10 7-10l8 0-3-15c-7-3-13-4-18-4-11 0-22 6-19 24l8 56c-3 0-7 1-11 2l2 17 36 0z m15 94c-1-7-8-11-15-11-8 0-14 6-13 14 1 7 8 11 15 11 8 0 14-5 13-14z m65 29c-23 23-53 35-85 35-33 0-63-12-86-35-23-23-35-53-35-85 0-33 12-63 35-85 23-23 53-35 85-35 33 0 63 12 85 35 23 23 35 53 35 85 1 31-11 61-34 85z m18-85c0-28-11-55-31-73-19-20-45-31-73-31-28 0-54 11-73 31-20 19-31 45-31 73 0 27 11 53 31 73 19 20 45 31 73 31 28 0 54-11 73-31 20-20 31-46 31-73z"/>
<glyph glyph-name="question" unicode="&#93;" d="M360 346c1 0 3-1 3-3l0-181c0-2-2-3-3-3l-182 0c-1 0-3 1-3 3l0 181c0 2 2 3 3 3l182 0m0 17l-182 0c-11 0-20-9-20-20l0-181c0-11 9-20 20-20l182 0c11 0 20 9 20 20l0 181c0 11-9 20-20 20z m-133-55c20 7 29 9 47 9 28 0 40-10 40-34l0-5c0-17-6-24-17-28-8-3-16-5-25-8l0-17-20 0-3 30c12 3 22 7 30 9 8 3 11 7 11 13l0 4c0 13-4 16-18 16-10 0-16-1-24-4l-2-12-19 0z m21-110c0 9 5 13 14 13 10 0 15-4 15-13 0-9-5-13-15-13-9 0-14 4-14 13z"/>
<glyph glyph-name="alert" unicode="&#43;" d="M267 369l120-207-240 0 120 207m0 18c-6 0-12-3-16-9l-119-207c-4-6-4-12 0-18 3-6 9-9 15-9l240 0c6 0 12 3 15 9 4 6 4 12 0 18l-119 207c-4 6-10 9-16 9z m-15-195c0 9 5 13 15 13 9 0 14-4 14-13 0-9-5-13-14-13-10 0-15 4-15 13z m28 96l-6-67-15 0-6 67 0 21 27 0z"/>
<glyph glyph-name="home" unicode="&#95;" d="M265 376l133-122-49 0 0-112-169 0 0 112-49 0 134 122m0 20c-5 0-10-2-14-5l-134-122c-6-5-8-14-5-22 3-8 11-13 19-13l29 0 0-92c0-11 9-20 20-20l169 0c11 0 20 9 20 20l0 92 29 0c9 0 16 5 19 13 3 8 1 17-5 22l-134 122c-4 3-9 5-13 5z"/>
<glyph glyph-name="error" unicode="&#61;" d="M258 143c-29 0-58 11-80 33-21 22-33 50-33 80 0 30 12 59 33 80 44 44 116 44 160 0 22-21 34-50 34-80 0-30-12-58-34-80-22-22-51-33-80-33z m0 211c-25 0-50-9-69-29-18-18-29-43-29-69 0-26 11-50 29-69 38-38 100-38 139 0 18 19 28 43 28 69 0 26-10 51-28 69-20 20-45 29-70 29z m13-98l32 33c4 3 4 9 0 12-3 3-9 3-12 0l-33-33-32 33c-4 3-9 3-12 0-4-3-4-9 0-12l32-33-32-32c-4-4-4-9 0-13 3-3 8-3 12 0l32 33 33-33c3-3 9-3 12 0 4 4 4 9 0 13z"/>
<glyph glyph-name="settings" unicode="&#64;" d="M352 276c-3 0-6-1-8-2-1 0-1 0-1 0-3 0-6-1-8-2-2 9-6 17-11 25 3 1 6 2 8 4 0 0 0 0 0 0 3 1 5 3 7 5 8 8 8 20 0 28-7 7-20 7-27-1-2-2-4-4-5-7 0 0 0 0 0 0-2-2-4-5-4-8-8 5-16 9-25 11 1 2 2 5 2 8 0 0 0 1 0 1 1 2 2 5 2 8 0 11-9 19-20 19-10 0-19-8-19-19 0-3 1-6 2-8 0-1 0-1 0-1 0-3 1-6 2-8-9-2-18-6-25-11-1 3-2 6-5 8 0 0 0 0 0 0-1 3-2 5-4 7-8 8-20 8-28 0-7-7-7-20 0-27 2-2 5-4 7-5 1 0 1 0 1 0 2-2 4-4 7-4-5-8-8-16-10-25-3 1-6 2-9 2 0 0 0 0 0 0-2 1-5 2-8 2-11 0-20-9-20-20 0-10 9-19 20-19 3 0 6 0 8 2 0 0 0-1 0-1 4 0 6 1 9 3 2-9 5-18 10-25-2-1-5-2-7-5 0 0 0 0 0 0-3-1-6-2-8-4-7-8-7-20 0-28 4-4 9-5 14-5 5 0 10 1 14 5 2 2 4 5 4 7 1 1 1 1 1 1 2 2 3 4 4 7 8-5 16-8 25-10-1-3-2-6-2-9 0 0 0 0 0 0-1-2-2-5-2-8 0-11 9-20 20-20 10 0 19 9 19 20 0 3-1 6-2 8 0 0 0 0 0 0 0 3-1 6-2 9 9 2 17 5 25 10 1-3 2-5 4-7 1 0 1 0 1-1 1-2 2-5 4-7 4-4 9-5 14-5 5 0 10 1 14 5 7 8 7 20 0 28-2 2-5 3-7 4-1 0-1 0-1 0-2 3-4 4-7 5 5 7 8 16 10 25 3-1 6-2 9-2 0 0 0 0 0 0 2-1 5-2 8-2 11 0 19 9 19 19 0 0 0 0 0 0 0 0 0 1 0 1 0 10-9 19-19 19z m-90-61c-22 0-41 19-41 41 0 23 19 41 41 41 23 0 41-18 41-41 0-22-18-41-41-41z"/>
<glyph glyph-name="trash" unicode="&#123;" d="M201 302l0-165c0-3-1-5-2-6-2-2-4-3-7-3l-18 0c-3 0-5 1-7 3-2 1-2 3-2 6l0 165c0 2 0 5 2 6 2 2 4 3 7 3l18 0c3 0 5-1 7-3 1-1 2-4 2-6z m73 0l0-165c0-3-1-5-2-6-2-2-4-3-7-3l-18 0c-3 0-5 1-7 3-1 1-2 3-2 6l0 165c0 2 1 5 2 6 2 2 4 3 7 3l18 0c3 0 5-1 7-3 1-1 2-4 2-6z m73 0l0-165c0-3 0-5-2-6-2-2-4-3-7-3l-18 0c-3 0-5 1-7 3-1 1-2 3-2 6l0 165c0 2 1 5 2 6 2 2 4 3 7 3l18 0c3 0 5-1 7-3 2-1 2-4 2-6z m37-207l0 271-256 0 0-271c0-4 1-8 2-12 1-3 3-6 4-7 2-2 3-3 3-3l238 0c0 0 1 1 3 3 1 1 3 4 4 7 1 4 2 8 2 12z m-192 307l128 0-14 34c-1 1-3 2-5 3l-90 0c-2-1-4-2-5-3z m265-9l0-18c0-3-1-5-2-7-2-1-4-2-7-2l-27 0 0-271c0-16-5-30-14-41-9-12-20-17-32-17l-238 0c-12 0-23 5-32 16-9 11-14 25-14 41l0 272-27 0c-3 0-5 1-7 2-1 2-2 4-2 7l0 18c0 3 1 5 2 7 2 1 4 2 7 2l88 0 20 48c3 7 8 13 16 18 7 5 15 7 22 7l92 0c7 0 15-2 22-7 8-5 13-11 16-18l20-48 88 0c3 0 5-1 7-2 1-2 2-4 2-7z"/>
<glyph glyph-name="object-group" unicode="&#57344;" d="M549 402l-37 0 0-292 37 0 0-110-110 0 0 37-366 0 0-37-110 0 0 110 37 0 0 292-37 0 0 110 110 0 0-37 366 0 0 37 110 0z m-74 73l0-36 37 0 0 36z m-475 0l0-36 37 0 0 36z m37-438l0 36-37 0 0-36z m402 36l0 37 36 0 0 292-36 0 0 37-366 0 0-37-36 0 0-292 36 0 0-37z m73-36l0 36-37 0 0-36z m-183 292l110 0 0-219-256 0 0 73-110 0 0 219 256 0z m-219-110l183 0 0 147-183 0z m292-73l0 147-73 0 0-110-110 0 0-37z"/>
<glyph glyph-name="cm" unicode="&#125;" d="M202 207c-5 0-10 1-14 4-4 2-8 5-11 10-2 4-4 9-6 15-1 6-2 12-2 20 0 7 1 13 2 19 2 6 4 11 6 16 3 4 7 7 11 10 4 2 9 3 14 3 5 0 9-1 12-2 4-2 6-4 8-6l-8-11c-1 1-3 3-5 4-2 1-4 2-7 2-3 0-5-1-7-3-3-2-4-4-6-7-1-3-2-7-3-11-1-5-1-9-1-14 0-5 0-10 1-14 1-4 2-8 3-11 2-3 3-5 6-7 2-2 5-3 8-3 3 0 5 1 7 2 2 1 4 2 5 4l7-12c-5-5-11-8-20-8z m55 92c4 3 9 5 15 5 4 0 6 0 9-2 2-1 4-2 6-4 1-2 3-4 4-6 1-2 1-4 2-7l0 0c1 2 2 5 4 7 1 2 3 4 5 6 2 2 4 3 7 4 2 1 5 2 8 2 5 0 9-1 12-3 3-2 6-5 7-8 2-4 3-7 4-12 0-4 1-9 1-13l0-58-16 0 0 58c0 3 0 5 0 8-1 3-1 5-2 7-1 2-2 4-4 6-2 1-4 2-7 2-2 0-5-1-7-3-2-1-4-3-5-5-2-3-3-6-4-9-1-4-1-7-1-12l0-52-15 0 0 58c0 3 0 5-1 8 0 3-1 5-1 7-1 3-3 4-4 6-2 1-4 2-7 2-3 0-5-1-7-2-2-2-4-4-6-6-1-3-2-6-3-9-1-3-1-7-1-11l0-53-15 0 0 68c0 4-1 7-1 12 0 4 0 8 0 11l15 0c0-3 0-6 0-9 1-3 1-5 1-7l0 0c1 6 4 10 7 14z"/>
<glyph glyph-name="msvg" unicode="&#126;" d="M228 299c4 3 8 5 15 5 3 0 6 0 8-2 2-1 4-2 6-4 2-2 3-4 4-6 1-2 2-4 2-7l1 0c0 2 2 5 3 7 1 2 3 4 5 6 2 2 4 3 7 4 2 1 5 2 9 2 4 0 8-1 12-3 3-2 5-5 7-8 1-4 2-7 3-12 1-4 1-9 1-13l0-58-16 0 0 58c0 3 0 5 0 8 0 3-1 5-2 7-1 2-2 4-4 6-1 1-4 2-7 2-2 0-5-1-7-3-2-1-4-3-5-5-2-3-3-6-4-9 0-4-1-7-1-12l0-52-15 0 0 58c0 3 0 5 0 8-1 3-1 5-2 7-1 3-2 4-4 6-2 1-4 2-7 2-2 0-5-1-7-2-2-2-4-4-5-6-2-3-3-6-4-9-1-3-1-7-1-11l0-53-15 0 0 68c0 4 0 7 0 12 0 4-1 8-1 11l15 0c0-3 0-6 1-9 0-3 0-5 0-7l0 0c1 6 4 10 8 14z"/>
<glyph glyph-name="deg" unicode="&#92;" d="M199 210l0 14-1 0c-1-4-4-9-7-12-3-3-8-5-14-5-5 0-9 1-12 4-4 2-7 6-9 10-3 4-5 9-6 15-1 6-2 13-2 20 0 7 1 13 2 19 1 6 3 11 5 16 3 4 6 7 10 10 3 2 7 3 12 3 5 0 10-1 13-4 4-3 7-7 8-12l0 0 0 59 16 0 0-137z m0 45c0 5-1 10-1 14-1 5-2 8-4 11-1 3-3 6-5 8-2 2-5 2-8 2-3 0-5 0-8-2-2-2-3-5-5-8-1-3-2-6-3-11-1-4-1-9-1-14 0-4 0-9 1-13 1-4 2-8 3-11 2-3 3-6 5-8 3-1 5-2 8-2 3 0 6 1 8 2 2 2 4 5 5 8 2 3 3 7 4 11 0 4 1 9 1 13z m49-3c0-5 1-9 1-13 1-3 2-7 4-9 1-3 3-5 5-7 3-2 5-2 8-2 5 0 8 1 10 3 3 3 5 6 6 9l12-6c-3-6-6-11-11-15-4-3-10-5-17-5-10 0-19 4-24 13-6 8-9 20-9 35 0 8 0 14 2 20 2 6 4 11 7 16 3 4 6 7 10 10 4 2 9 3 14 3 5 0 10-1 14-3 4-3 7-6 9-10 3-4 5-9 6-14 1-6 1-12 1-18l0-7-48 0z m33 12c0 8-2 14-4 19-3 5-6 7-12 7-3 0-6 0-8-2-2-2-3-4-5-7-1-3-2-6-3-9 0-3-1-6-1-8z m79 37l15 0 0-91c0-6-1-12-2-17-1-5-3-10-6-14-2-4-6-7-10-9-5-2-10-3-16-3-7 0-12 1-18 4-5 2-10 6-13 10l9 11c3-3 6-6 10-8 3-2 8-3 12-3 4 0 7 0 9 2 3 1 5 3 6 6 1 3 2 6 3 9 1 4 1 8 1 12l0 14-1 0c-1-5-3-9-7-12-3-3-7-5-14-5-4 0-8 1-12 4-4 2-7 6-9 10-3 4-4 9-6 15-1 6-2 13-2 20 0 7 1 13 2 19 1 6 3 11 6 15 2 5 5 8 9 10 3 3 8 4 12 4 6 0 10-2 14-5 3-3 6-6 7-11l1 0 0 13z m0-45c0 4 0 9-1 13-1 5-2 8-3 11-2 4-4 6-6 8-2 2-5 3-8 3-3 0-5-1-7-3-2-2-4-4-5-8-2-3-3-6-4-11 0-4-1-9-1-13 0-5 1-10 1-14 1-4 2-8 4-11 1-3 3-6 5-8 2-1 4-2 7-2 3 0 6 1 8 2 2 2 4 5 6 8 1 3 2 7 3 11 1 4 1 9 1 14z"/>
<glyph glyph-name="px" unicode="&#124;" d="M206 301l0-14 1 0c1 5 3 9 7 12 3 3 8 5 14 5 4 0 9-1 12-4 4-2 7-6 9-10 3-4 5-9 6-15 1-6 2-13 2-20 0-7-1-13-2-19-1-6-3-11-6-15-2-5-5-8-9-10-3-3-7-4-12-4-5 0-10 1-14 5-3 3-6 7-7 11l0 0 0-56-16 0 0 134z m0-45c0-5 1-10 1-14 1-4 2-8 4-11 1-3 3-6 5-8 2-1 5-2 8-2 3 0 5 1 7 2 3 2 4 5 6 8 1 3 2 7 3 11 0 4 1 9 1 14 0 4-1 9-1 13-1 5-2 8-3 11-2 3-3 6-6 8-2 2-4 2-7 2-3 0-6 0-8-2-2-2-4-5-5-8-2-3-3-6-4-11 0-4-1-9-1-13z m86 2l-23 43 17 0 15-32 13 32 17 0-22-43 24-48-17 0-16 36-16-36-17 0z"/>
<glyph glyph-name="m-sq" unicode="&#57345;" d="M204 299c4 3 8 5 15 5 3 0 6 0 8-2 2-1 4-2 6-4 2-2 3-4 4-6 1-2 2-4 2-7l1 0c0 2 2 5 3 7 1 2 3 4 5 6 2 2 4 3 7 4 2 1 5 2 9 2 4 0 8-1 12-3 3-2 5-5 7-8 1-4 2-7 3-12 1-4 1-9 1-13l0-58-16 0 0 58c0 3 0 5 0 8 0 3-1 5-2 7-1 2-2 4-4 6-1 1-4 2-7 2-2 0-5-1-7-3-2-1-4-3-5-5-2-3-3-6-4-9 0-4-1-7-1-12l0-52-15 0 0 58c0 3 0 5 0 8-1 3-1 5-2 7-1 3-2 4-4 6-2 1-4 2-7 2-2 0-5-1-7-2-2-2-4-4-5-6-2-3-3-6-4-9-1-3-1-7-1-11l0-53-15 0 0 68c0 4 0 7 0 12 0 4-1 8-1 11l15 0c0-3 0-6 1-9 0-3 0-5 0-7l0 0c1 6 4 10 8 14z m129 44c0-2-1-4-1-5-1-2-1-4-2-6-1-1-2-3-3-5-2-2-3-3-4-5l-14-20 23 0 0-7-32 0 0 8 18 24c2 3 3 6 4 8 2 3 2 5 2 8 0 3-1 5-2 7-1 3-3 4-6 4-2 0-4-1-6-3-2-2-3-4-3-7l-8 1c1 5 3 9 6 12 3 2 7 4 12 4 2 0 4-1 6-2 2-1 4-2 5-3 2-2 3-4 3-6 1-2 2-4 2-7z"/>
<glyph glyph-name="m-cubed" unicode="&#57346;" d="M204 299c4 3 8 5 15 5 3 0 6 0 8-2 2-1 4-2 6-4 2-2 3-4 4-6 1-2 2-4 2-7l1 0c0 2 2 5 3 7 1 2 3 4 5 6 2 2 4 3 7 4 2 1 5 2 9 2 4 0 8-1 12-3 3-2 5-5 7-8 1-4 2-7 3-12 1-4 1-9 1-13l0-58-16 0 0 58c0 3 0 5 0 8 0 3-1 5-2 7-1 2-2 4-4 6-1 1-4 2-7 2-2 0-5-1-7-3-2-1-4-3-5-5-2-3-3-6-4-9 0-4-1-7-1-12l0-52-15 0 0 58c0 3 0 5 0 8-1 3-1 5-2 7-1 3-2 4-4 6-2 1-4 2-7 2-2 0-5-1-7-2-2-2-4-4-5-6-2-3-3-6-4-9-1-3-1-7-1-11l0-53-15 0 0 68c0 4 0 7 0 12 0 4-1 8-1 11l15 0c0-3 0-6 1-9 0-3 0-5 0-7l0 0c1 6 4 10 8 14z m130 14c0-3-1-5-2-7 0-3-2-5-3-7-2-1-3-3-6-4-2-1-4-1-7-1-5 0-8 1-11 3-4 2-6 6-7 10l7 2c1-2 2-4 4-6 2-1 4-2 7-2 1 0 3 0 4 1 1 1 2 2 3 3 1 1 2 2 2 4 1 1 1 3 1 4 0 4-1 7-3 9-3 2-5 3-9 3l-2 0 0 7 2 0c3 0 6 1 8 3 1 2 2 5 2 8 0 2 0 3 0 4 0 1-1 2-2 3 0 1-1 2-2 3-1 1-3 1-4 1-2 0-4-1-5-2-2-1-3-2-4-4l-7 2c1 2 2 3 3 5 1 1 3 2 4 3 1 1 3 2 4 2 2 1 4 1 5 1 3 0 5-1 7-1 2-1 3-2 5-4 1-1 2-3 3-5 1-2 1-4 1-7 0-2 0-3 0-5-1-2-1-3-2-4-1-2-2-3-3-4-1-1-3-1-4-2l0 0c3-1 6-3 8-6 2-2 3-6 3-10z"/>
<glyph glyph-name="acceleration" unicode="&#57347;" d="M207 350c3 2 8 3 14 3 3 0 5 0 8-1 2 0 4-1 5-2 2-1 3-2 4-4 1-1 2-3 2-4l0 0c1 1 2 3 3 4 2 1 3 3 5 4 2 1 4 2 7 2 2 1 5 1 8 1 5 0 8 0 11-1 3-2 5-3 7-5 1-2 2-5 3-7 1-3 1-6 1-8l0-35-15 0 0 35c0 1 0 3 0 4 0 2-1 4-2 5-1 1-2 2-3 3-2 1-4 1-7 1-2 0-4 0-6-1-2-1-4-2-6-4-1-1-2-3-3-5-1-2-1-4-1-7l0-31-14 0 0 35c0 1-1 3-1 4 0 2-1 3-1 5-1 1-2 2-4 3-2 1-4 1-7 1-2 0-4 0-6-1-2-1-4-2-5-4-2-1-3-3-3-5-1-2-2-4-2-7l0-32-14 0 0 41c0 3 0 5 0 8 0 2 0 4-1 6l14 0c0-1 1-3 1-5 0-2 0-3 0-4l0 0c2 3 4 6 8 8z m112-92c0-2 0-4-1-5 0-1-1-3-2-4 0-2-1-3-2-4-1-2-2-3-3-5l-11-15 18 0 0-6-25 0 0 6 14 20c2 2 3 4 4 6 1 2 1 4 1 6 0 3 0 5-1 6-1 2-3 3-5 3-2 0-4-1-5-2-1-2-2-3-2-6l-7 1c1 4 3 7 5 9 2 2 5 3 9 3 2 0 4 0 6-1 1 0 3-1 4-3 1-1 2-2 2-4 1-2 1-3 1-5z m-112-18c-1 1-2 2-3 3-1 1-3 1-5 1-1 0-3 0-4-2-1-1-2-3-2-5 0-2 1-3 2-4 1-1 3-2 6-3 1-1 2-1 3-2 2-1 3-2 4-3 1-1 2-2 2-4 1-1 1-3 1-5 0-2 0-4-1-6-1-2-2-3-3-5-1-1-3-2-4-2-2-1-4-1-6-1-3 0-5 0-7 1-3 1-4 3-6 5l5 5c1-2 2-3 3-4 2 0 3-1 5-1 2 0 4 1 5 2 1 2 2 3 2 6 0 1 0 2-1 3 0 1-1 2-2 2 0 1-1 1-2 2-1 0-2 1-3 1-2 1-3 1-4 2-1 0-2 1-3 2-1 1-1 2-2 4 0 1-1 3-1 5 0 2 1 4 1 5 1 2 2 3 3 5 1 1 3 2 4 2 2 1 4 1 6 1 2 0 5 0 7-1 2-1 3-2 5-4z m18-16c0-2 0-4 0-6 1-2 1-4 2-5 1-1 2-3 3-3 1-1 3-2 4-2 2 0 4 1 5 2 1 2 2 3 3 5l6-3c-2-3-3-6-6-8-2-1-5-2-8-2-5 0-10 2-13 6-3 4-4 10-4 18 0 4 0 7 1 10 1 3 2 6 4 8 1 2 3 3 5 5 2 1 4 1 7 1 2 0 5 0 7-1 2-2 3-3 5-5 1-2 2-5 2-7 1-3 1-6 1-9l0-4-24 0z m16 6c0 4 0 7-2 10-1 2-3 4-6 4-1 0-3-1-4-2-1-1-2-2-2-3-1-2-1-3-2-5 0-1 0-2 0-4l16 0z m31-28c-3 0-5 0-7 1-2 2-4 3-5 5-2 3-3 5-3 8-1 3-1 6-1 10 0 4 0 7 1 10 0 3 1 5 3 8 1 2 3 3 5 5 2 1 5 1 7 1 3 0 5 0 6-1 2-1 3-1 4-2l-4-6c0 1-1 1-2 2-1 0-2 1-4 1-1 0-2-1-3-2-1-1-2-2-3-3-1-2-1-4-2-6 0-2 0-4 0-7 0-2 0-5 0-7 1-2 1-4 2-5 1-2 2-3 3-4 1-1 2-1 4-1 1 0 3 0 4 1 1 0 1 1 2 2l4-6c-3-3-6-4-11-4z m17 74l-107 0c-2 0-4 2-4 4 0 2 2 4 4 4l107 0c2 0 4-2 4-4 0-2-2-4-4-4z"/>
<glyph glyph-name="particles" unicode="&#57348;" d="M332 229c0 12 10 23 23 23 13 0 23-11 23-23 0-13-10-24-23-24-13 0-23 11-23 24z m-54-68c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z m-62-18c0 13 11 23 24 23 12 0 23-10 23-23 0-13-11-23-23-23-13 0-24 10-24 23z m-46 60c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z m81-138c-5 3-8 9-9 15-1 6 1 12 4 17 4 5 9 8 15 9 6 1 13 0 18-4 5-4 8-9 9-15 1-6-1-13-4-18-4-4-9-8-16-9-6-1-12 1-17 5z m-183 201c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z m111 46c0 13 10 24 23 24 13 0 23-11 23-24 0-12-10-23-23-23-13 0-23 11-23 23z m71-94c0 13 11 23 24 23 12 0 23-10 23-23 0-13-11-23-23-23-13 0-24 10-24 23z m163 52c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-12 0-23 10-23 23z m-111 38c0 13 11 23 23 23 13 0 24-10 24-23 0-13-11-23-24-23-12 0-23 10-23 23z m-170 90c0 13 11 24 24 24 12 0 23-11 23-24 0-12-11-23-23-23-13 0-24 11-24 23z m235-23c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z"/>
<glyph glyph-name="voxels" unicode="&#57349;" d="M434 379l-85 49c-4 2-10 2-14 0l-77-45-79 46c-4 2-10 2-14 0l-85-49c-4-3-7-7-7-12l0-98c0-5 3-10 7-13l78-45 0-89c0-5 3-10 7-12l85-49c2-2 5-2 7-2 2 0 5 0 7 2l85 49c4 2 7 7 7 12l0 88 78 45c5 3 7 7 7 12l0 99c0 5-2 9-7 12z m-21-88l-59 34 0 68 59-35z m-69-55l-73 42 0 80 59 35 0-68-29-17c-6-3-8-11-4-16 2-4 6-6 10-6 2 0 4 0 6 1l29 17 60-34z m-75-57l0 66 59-34 0-66z m-26 113l-59 34 0 68 59-34z m-142 68l59 34 0-68-31-18c-6-3-8-11-5-17 2-3 7-6 11-6 2 0 4 1 6 2l31 18 59-34-60-34-70 41z m156-270l-71 41 0 81 59 34 0-67-28-16c-6-3-8-11-5-17 3-3 7-6 11-6 2 0 4 1 6 2l28 16 59-34z"/>
<glyph glyph-name="lock" unicode="&#57350;" d="M389 233l0 62c0 68-55 124-123 124-69 0-124-56-124-124l0-62c-24-4-44-26-44-52l0-74c0-29 24-52 52-52l230 0c29 0 53 23 53 52l0 74c0 26-18 48-44 52z m-123 129c37 0 67-30 67-67l0-61-135 0 0 61c0 37 31 67 68 67z"/>
<glyph glyph-name="visible" unicode="&#57351;" d="M258 116c-55 0-106 17-147 51-31 25-47 51-47 52-4 7-4 16 1 23 2 4 66 98 195 96 133-3 192-93 195-97 4-6 4-15 0-22 0-1-15-27-46-53-29-23-79-50-151-50 0 0 0 0 0 0z m-148 113c7-7 17-18 30-29 34-27 73-40 118-40 0 0 0 0 0 0 47 0 88 13 122 40 13 10 23 21 29 29-7 7-16 16-30 26-34 25-74 38-119 38-81 2-130-42-150-64z m-27 1z m227-4c0-25-21-46-47-46-26 0-47 21-47 46 0 26 21 47 47 47 26 0 47-21 47-47z"/>
<glyph glyph-name="model" unicode="&#57352;" d="M494 395c-2 5-8 8-13 7l-90-16 45 72c3 5 2 11-1 15-4 4-10 5-15 3l-213-98c-15 5-72 27-111 43 0 0-1 0-1 0 0 0 0 0 0 0 0 0-1 0-1 1 0 0-1 0-1 0 0 0-1 0-1 0 0 0 0 0 0 0-1 0-1 0-2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0 0 0-1-1 0-1 0-2 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0-1 0-1-1 0-2 0-3-1 0 0 0 0 0 0 0-1-1-1-1-1 0 0 0 0 0 0 0 0 0-1 0-1-1 0-1 0-1 0 0 0 0 0 0-1 0 0 0 0-1-1l-27-52-33-40c-3-4-3-10 0-15 2-3 6-5 10-5 1 0 2 0 4 1l50 17 40 2-26-51c-3-4-2-9 1-13 1-1 26-30 52-58 15-17 28-30 38-40 6-6 11-11 15-14l-16-61-46-18c-6-3-9-10-6-16 2-5 6-8 11-8 1 0 3 1 4 1l45 18 17-15c2-3 5-4 8-4 4 0 7 2 9 5 5 5 4 12-1 17l-17 15 16 61 76-90c2-2 6-4 9-4 1 0 2 0 3 0l85 23c5 2 8 6 9 11 0 5-2 9-7 12l-136 72 45 91 178 123c5 3 6 9 4 15z m-200-117l-122 55 41 21 181 83z m-148 73l-24 33c16-6 39-15 54-21z m-59-6l-9 13 15 29 27-38 2-2z m36-77l23 44c18-45 35-91 47-121-19 20-45 49-70 77z m194-194l-57 68 105-55z m-94 101c-5 14-42 104-30 77 0-2-21 49-28 66l121-59-48-95z m108 120l43 63 70 16z"/>
<glyph glyph-name="forward" unicode="&#68;" d="M330 278l-95 70c-5 4-12 5-18 2-5-3-9-9-9-16l0-150c0-7 4-13 10-16 2-1 5-2 7-2 4 0 8 2 11 5l95 79c4 4 6 9 6 14-1 5-3 10-7 14"/>
<glyph glyph-name="avatar-2" unicode="&#57353;" d="M256 88c-93 0-169 75-169 168 0 93 76 169 169 169 93 0 169-76 169-169 0-93-76-168-169-168z m0 316c-81 0-148-66-148-148 0-81 67-147 148-147 81 0 148 66 148 147 0 82-67 148-148 148z m97-90l-1 1c-3 3-7 4-10 4-1 0-61-9-86-9-1 0-1 0-2 0-25 0-87 10-87 10-5 0-10-2-13-6l-1-2c-2-3-2-7-1-10 1-4 3-6 6-8 12-5 49-20 60-22 2 0 5 0 6-7 1-8-3-46-7-65-5-17-13-40-13-41-2-6 1-13 7-15l8-3c3-1 6-1 9 1 3 1 5 4 6 7l21 65 20-67c1-3 3-6 6-7 2-1 4-1 5-1 2 0 3 0 5 0l7 3c5 2 8 8 7 14 0 0-6 24-11 44-3 12-4 30-5 45 0 9-1 16-2 22 0 1 0 4 5 5 0 0 1 0 2 0l55 22c4 2 6 5 7 9 1 4 0 8-3 11z m-68 37c0-16-13-29-29-29-16 0-29 13-29 29 0 16 13 29 29 29 16 0 29-13 29-29z"/>
<glyph glyph-name="arrow-dn" unicode="&#53;" d="M258 219l-43 55 86 0z"/>
<glyph glyph-name="arrow-up" unicode="&#54;" d="M258 283l43-55-86 0z"/>
<glyph glyph-name="time" unicode="&#57354;" d="M256 390c-73 0-132-59-132-132 0-73 59-132 132-132 73 0 132 59 132 132 0 73-59 132-132 132z m60-162l1 0-64 0c-2 0-4 0-6 1-8 2-15 10-15 19l0 0 2 92c0 11 9 20 19 20 11 0 20-9 20-20l-1-72 44 0c11 0 20-9 20-20 0-10-9-20-20-20z"/>
<glyph glyph-name="transparency" unicode="&#57355;" d="M349 349c1 0 3-1 3-3l0-181c0-2-2-3-3-3l-182 0c-1 0-3 1-3 3l0 181c0 2 2 3 3 3l182 0m0 17l-182 0c-11 0-20-9-20-20l0-181c0-11 9-20 20-20l182 0c11 0 20 9 20 20l0 181c0 11-9 20-20 20z m-187-108l96 0 0-96-96 0z m94 94l96 0 0-95-96 0z"/>
<glyph glyph-name="unmuted" unicode="&#71;" d="M298 255c-1-8-2-17-3-25-2-7-4-15-6-22-2-6-1-11 3-14 7-6 16-3 19 6 8 23 12 47 9 71-1 14-4 27-9 40-3 7-12 10-18 5-5-3-7-8-4-14 5-15 8-31 9-47m-35 81c0 12-5 20-16 24-7 3-19 2-27-7-11-12-22-24-34-37-1-1-3-1-5-2-4 0-7 0-11 0-3 0-5 0-8 0-15 0-26-10-26-23 0-28 0-42 0-71 0-11 10-22 21-23 4 0 9 0 14 0 3-1 7-1 10-1 2 0 4-1 5-2 10-11 22-23 33-36 6-6 12-9 20-9 3 0 5 1 8 2 11 3 16 12 16 24 0 22 0 39 0 59l0 43c0 19 0 59 0 59m-26-3l0-155-1 1c-3 3-5 5-7 7l-8 9c-8 9-16 17-23 25-2 2-4 2-6 2-4 1-9 1-13 1-2 0-4 0-7 0l-9 0 0 65 2 0c3 0 5 0 8 0 6 0 12 0 18 0 0 0 0 0 0 0 3 0 5 1 8 4 10 10 20 21 30 32z m140-78c0 3-1 6-1 10-1 7-1 14-2 22-3 15-7 31-14 47-2 4-5 7-9 8-4 1-8 0-11-3-5-4-6-10-4-16 9-22 13-42 14-64 1-24-4-49-14-72-1-4-2-8 0-11 1-3 4-6 7-7 2-1 4-1 5-1 6 0 10 3 13 9 10 25 15 51 16 78z"/>
<glyph glyph-name="user" unicode="&#57356;" d="M257 406c-83 0-151-68-151-151 0-83 68-150 151-150 83 0 150 67 150 150 0 83-67 151-150 151z m0-282c-73 0-132 59-132 131 0 73 59 132 132 132 73 0 132-59 132-132 0-72-59-131-132-131z m45 179c0-24-19-43-42-43-24 0-43 19-43 43 0 23 19 42 43 42 23 0 42-19 42-42z m-10-71l-62 0c-25 0-53-12-53-37l0-18c26-23 50-32 77-32 32 0 69 14 85 33l0 17c0 25-22 37-47 37z"/>
<glyph glyph-name="edit-pencil" unicode="&#57357;" d="M341 403c9 9 18 19 28 28 4 4 9 3 12-1 17-16 33-32 50-49 3-4 4-8 0-12-10-10-20-19-29-29-20 21-41 42-61 63z m-25-23c-1-2-2-4-3-5-9-9-17-17-26-26-49-49-153-152-175-175-5-5-9-11-10-19-3-22-6-43-10-66 3 1 4 1 6 1 20 4 41 8 62 13 4 1 8 3 11 6 21 21 109 108 163 162 13 14 27 28 41 42 2 1 3 3 4 3-21 21-41 42-63 64z m-158-231c-6 6-12 12-17 17 21 21 137 137 177 177 5-5 11-11 16-17-7-7-14-14-21-22-50-50-138-137-155-155z"/>
<glyph glyph-name="muted" unicode="&#72;" d="M377 274l-57-57c-5-5-13-5-18 0-5 5-5 13 0 18l57 57c5 5 13 5 18 0 5-5 5-13 0-18m-18-57l-57 57c-5 5-5 13 0 18 5 5 13 5 18 0l57-57c5-5 5-13 0-18-5-5-13-5-18 0m-95 120c0 12-6 21-17 25-8 3-20 3-29-7-11-12-23-25-35-37-1-2-3-2-4-3-4 0-8 0-12 0-3 0-6 0-8 0-15 0-27-11-27-24 0-29 0-42 0-73 0-11 10-22 21-23 5-1 10-1 15-1 4 0 7 0 11 0 1 0 3-1 4-2 11-12 23-24 35-37 5-7 12-10 20-10 3 0 6 1 9 2 11 4 17 13 17 25 0 22 0 40 0 60l0 45c0 20 0 60 0 60m-28-3l0-159-1 1c-3 3-5 5-7 8l-9 9c-8 9-16 17-24 26-1 1-3 2-5 2-5 0-9 0-14 0-2 0-5 0-7 0l-10 0 0 67 3 0c3 0 5 0 8 0 6 0 12 0 19 0 0 0 0 0 0 0 3 0 5 1 8 4 10 11 20 22 31 34z"/>
<glyph glyph-name="vol-0" unicode="&#57358;" d="M109 352c-2-2-5-4-8-4-7 0-13 0-18 0-2 0-4 0-7 0-2 0-4 0-6 0-23 0-41-16-41-37 0-44 0-65 0-113 1-17 15-33 32-35 8-1 16-1 23-1l1 0c5 0 11 0 16 0 3 0 6-2 8-4 15-15 35-37 54-58 8-9 18-14 30-14 4 0 8 1 12 2 16 6 23 19 23 39l0 162c0 31 2 94 2 95 0 19-9 31-25 37-11 5-29 4-42-10-20-23-37-42-54-59z m66 14l15 18 0-259-4 7c-2 2-4 4-6 6-1 2-3 4-5 6l-13 14c-3 3-6 6-9 10-9 10-18 20-27 30-2 2-6 3-8 3-6 0-13 0-21 0l-29 0 0 106 7 0c3 0 5 0 8 0 1 0 2 0 4 0 5 0 11 0 16 0 4 0 9 0 13 0l1 0c4 0 7 2 11 6 7 8 14 16 21 23 8 10 17 20 26 30z"/>
<glyph glyph-name="vol-1" unicode="&#57359;" d="M109 352c-2-2-5-4-8-4-7 0-13 0-18 0-2 0-4 0-7 0-2 0-4 0-6 0-23 0-41-16-41-37 0-44 0-65 0-113 1-17 15-33 32-35 8-1 16-1 23-1l1 0c5 0 11 0 16 0 3 0 6-2 8-4 15-15 35-37 54-58 8-9 18-14 30-14 4 0 8 1 12 2 16 6 23 19 23 39l0 162c0 31 2 94 2 95 0 19-9 31-25 37-11 5-29 4-42-10-20-23-37-42-54-59z m66 14l15 18 0-259-4 7c-2 2-4 4-6 6-1 2-3 4-5 6l-13 14c-3 3-6 6-9 10-9 10-18 20-27 30-2 2-6 3-8 3-6 0-13 0-21 0l-29 0 0 106 7 0c3 0 5 0 8 0 1 0 2 0 4 0 5 0 11 0 16 0 4 0 9 0 13 0l1 0c4 0 7 2 11 6 7 8 14 16 21 23 8 10 17 20 26 30z m89-110c0 32-15 58-34 58-19 0-34-26-34-58 0-32 15-58 34-58 19 0 34 26 34 58z"/>
<glyph glyph-name="vol-2" unicode="&#57360;" d="M304 254c-2-13-3-26-5-39-2-12-6-24-10-36-3-10-1-17 5-22 12-9 26-5 31 9 14 37 19 75 15 115-3 21-8 43-16 63-4 12-18 16-28 9-8-5-10-13-7-24 9-24 14-49 15-75m-195 98c-2-2-5-4-8-4-7 0-13 0-18 0-2 0-4 0-7 0-2 0-4 0-6 0-23 0-41-16-41-37 0-44 0-65 0-113 1-17 15-33 32-35 8-1 16-1 23-1l1 0c5 0 11 0 16 0 3 0 6-2 8-4 15-15 35-37 54-58 8-9 18-14 30-14 4 0 8 1 12 2 16 6 23 19 23 39l0 162c0 31 2 94 2 95 0 19-9 31-25 37-11 5-29 4-42-10-20-23-37-42-54-59z m66 14l15 18 0-259-4 7c-2 2-4 4-6 6-1 2-3 4-5 6l-13 14c-3 3-6 6-9 10-9 10-18 20-27 30-2 2-6 3-8 3-6 0-13 0-21 0l-29 0 0 106 7 0c3 0 5 0 8 0 1 0 2 0 4 0 5 0 11 0 16 0 4 0 9 0 13 0l1 0c4 0 7 2 11 6 7 8 14 16 21 23 8 10 17 20 26 30z m89-110c0 32-15 58-34 58-19 0-34-26-34-58 0-32 15-58 34-58 19 0 34 26 34 58z"/>
<glyph glyph-name="vol-3" unicode="&#57361;" d="M304 254c-2-13-3-26-5-39-2-12-6-24-10-36-3-10-1-17 5-22 12-9 26-5 31 9 14 37 19 75 15 115-3 21-8 43-16 63-4 12-18 16-28 9-8-5-10-13-7-24 9-24 14-49 15-75m-195 98c-2-2-5-4-8-4-7 0-13 0-18 0-2 0-4 0-7 0-2 0-4 0-6 0-23 0-41-16-41-37 0-44 0-65 0-113 1-17 15-33 32-35 8-1 16-1 23-1l1 0c5 0 11 0 16 0 3 0 6-2 8-4 15-15 35-37 54-58 8-9 18-14 30-14 4 0 8 1 12 2 16 6 23 19 23 39l0 162c0 31 2 94 2 95 0 19-9 31-25 37-11 5-29 4-42-10-20-23-37-42-54-59z m66 14l15 18 0-259-4 7c-2 2-4 4-6 6-1 2-3 4-5 6l-13 14c-3 3-6 6-9 10-9 10-18 20-27 30-2 2-6 3-8 3-6 0-13 0-21 0l-29 0 0 106 7 0c3 0 5 0 8 0 1 0 2 0 4 0 5 0 11 0 16 0 4 0 9 0 13 0l1 0c4 0 7 2 11 6 7 8 14 16 21 23 8 10 17 20 26 30z m245-112c-1 6-1 11-2 16-1 11-2 23-3 35-4 25-12 51-22 76-3 6-8 11-14 12-7 2-13 1-19-4-8-6-10-15-5-26 13-34 21-67 21-102 1-39-6-78-22-116-2-6-3-13 0-18 2-5 6-9 12-11 2-1 5-2 8-2 8 0 16 6 20 16 16 39 25 81 26 124z m-156 2c0 32-15 58-34 58-19 0-34-26-34-58 0-32 15-58 34-58 19 0 34 26 34 58z"/>
<glyph glyph-name="vol-4" unicode="&#57362;" d="M304 254c-2-13-3-26-5-39-2-12-6-24-10-36-3-10-1-17 5-22 12-9 26-5 31 9 14 37 19 75 15 115-3 21-8 43-16 63-4 12-18 16-28 9-8-5-10-13-7-24 9-24 14-49 15-75m-195 98c-2-2-5-4-8-4-7 0-13 0-18 0-2 0-4 0-7 0-2 0-4 0-6 0-23 0-41-16-41-37 0-44 0-65 0-113 1-17 15-33 32-35 8-1 16-1 23-1l1 0c5 0 11 0 16 0 3 0 6-2 8-4 15-15 35-37 54-58 8-9 18-14 30-14 4 0 8 1 12 2 16 6 23 19 23 39l0 162c0 31 2 94 2 95 0 19-9 31-25 37-11 5-29 4-42-10-20-23-37-42-54-59z m66 14l15 18 0-259-4 7c-2 2-4 4-6 6-1 2-3 4-5 6l-13 14c-3 3-6 6-9 10-9 10-18 20-27 30-2 2-6 3-8 3-6 0-13 0-21 0l-29 0 0 106 7 0c3 0 5 0 8 0 1 0 2 0 4 0 5 0 11 0 16 0 4 0 9 0 13 0l1 0c4 0 7 2 11 6 7 8 14 16 21 23 8 10 17 20 26 30z m245-112c-1 6-1 11-2 16-1 11-2 23-3 35-4 25-12 51-22 76-3 6-8 11-14 12-7 2-13 1-19-4-8-6-10-15-5-26 13-34 21-67 21-102 1-39-6-78-22-116-2-6-3-13 0-18 2-5 6-9 12-11 2-1 5-2 8-2 8 0 16 6 20 16 16 39 25 81 26 124z m-156 2c0 32-15 58-34 58-19 0-34-26-34-58 0-32 15-58 34-58 19 0 34 26 34 58z m173-186c-4 0-7 1-10 3-10 5-13 18-8 27 45 79 61 208-4 322-6 9-3 22 7 27 10 6 22 2 27-7 74-127 55-273 5-362-4-6-10-10-17-10z"/>
<glyph glyph-name="vol-x-0" unicode="&#57363;" d="M209 196l-57 58 56 57c9 9 9 22 0 31-8 8-22 8-30-1l-57-57-57 58c-8 8-22 8-30-1-9-8-9-22 0-30l57-58-53-58c-9-9-9-22 0-31 8-8 22-8 30 0l54 58 57-57c8-8 22-8 30 0 9 9 8 23 0 31z"/>
<glyph glyph-name="vol-x-1" unicode="&#57364;" d="M248 305l-48-48 48-50c10 10 16 28 16 49 0 21-6 39-16 49z m-39-109l-57 58 56 57c9 9 9 22 0 31-8 8-22 8-30-1l-57-57-57 58c-8 8-22 8-30-1-9-8-9-22 0-30l57-58-53-58c-9-9-9-22 0-31 8-8 22-8 30 0l54 58 57-57c8-8 22-8 30 0 9 9 8 23 0 31z"/>
<glyph glyph-name="vol-x-2" unicode="&#57365;" d="M304 254c-2-13-3-26-5-39-2-12-6-24-10-36-3-10-1-17 5-22 12-9 26-5 31 9 14 37 19 75 15 115-3 21-8 43-16 63-4 12-18 16-28 9-8-5-10-13-7-24 9-24 14-49 15-75m-56 51l-48-48 48-50c10 10 16 28 16 49 0 21-6 39-16 49z m-39-109l-57 58 56 57c9 9 9 22 0 31-8 8-22 8-30-1l-57-57-57 58c-8 8-22 8-30-1-9-8-9-22 0-30l57-58-53-58c-9-9-9-22 0-31 8-8 22-8 30 0l54 58 57-57c8-8 22-8 30 0 9 9 8 23 0 31z"/>
<glyph glyph-name="vol-x-3" unicode="&#57366;" d="M304 254c-2-13-3-26-5-39-2-12-6-24-10-36-3-10-1-17 5-22 12-9 26-5 31 9 14 37 19 75 15 115-3 21-8 43-16 63-4 12-18 16-28 9-8-5-10-13-7-24 9-24 14-49 15-75m116 0c-1 6-1 11-2 16-1 11-2 23-3 35-4 25-12 51-22 76-3 6-8 11-14 12-7 2-13 1-19-4-8-6-10-15-5-26 13-34 21-67 21-102 1-39-6-78-22-116-2-6-3-13 0-18 2-5 6-9 12-11 2-1 5-2 8-2 8 0 16 6 20 16 16 39 25 81 26 124z m-172 51l-48-48 48-50c10 10 16 28 16 49 0 21-6 39-16 49z m-39-109l-57 58 56 57c9 9 9 22 0 31-8 8-22 8-30-1l-57-57-57 58c-8 8-22 8-30-1-9-8-9-22 0-30l57-58-53-58c-9-9-9-22 0-31 8-8 22-8 30 0l54 58 57-57c8-8 22-8 30 0 9 9 8 23 0 31z"/>
<glyph glyph-name="vol-x-4" unicode="&#57367;" d="M304 254c-2-13-3-26-5-39-2-12-6-24-10-36-3-10-1-17 5-22 12-9 26-5 31 9 14 37 19 75 15 115-3 21-8 43-16 63-4 12-18 16-28 9-8-5-10-13-7-24 9-24 14-49 15-75m116 0c-1 6-1 11-2 16-1 11-2 23-3 35-4 25-12 51-22 76-3 6-8 11-14 12-7 2-13 1-19-4-8-6-10-15-5-26 13-34 21-67 21-102 1-39-6-78-22-116-2-6-3-13 0-18 2-5 6-9 12-11 2-1 5-2 8-2 8 0 16 6 20 16 16 39 25 81 26 124z m17-184c-4 0-7 1-10 3-10 5-13 18-8 27 45 79 61 208-4 322-6 9-3 22 7 27 10 6 22 2 27-7 74-127 55-273 5-362-4-6-10-10-17-10z m-189 235l-48-48 48-50c10 10 16 28 16 49 0 21-6 39-16 49z m-39-109l-57 58 56 57c9 9 9 22 0 31-8 8-22 8-30-1l-57-57-57 58c-8 8-22 8-30-1-9-8-9-22 0-30l57-58-53-58c-9-9-9-22 0-31 8-8 22-8 30 0l54 58 57-57c8-8 22-8 30 0 9 9 8 23 0 31z"/>
<glyph glyph-name="share-ext" unicode="&#57368;" d="M135 133c0 71 0 127 0 198 51 0 85 0 136 0 1 0 1-1 1-1-4-3-7-7-11-10-4-4-8-10-13-12-5-2-11 0-17 0-28 0-39 0-67 0-2 0-3 0-5 0 0-56 0-96 0-151 55 0 94 0 149 0 0 2 0 3 0 5 0 32 0 50 0 82 0 3 1 5 3 7 7 7 14 13 20 20 0-51 0-87 0-138-71 0-125 0-196 0z m202 222c-21 0-42 0-64 0 0 8 0 16 0 24 35 0 71 0 106 0 0-36 0-71 0-107-8 0-15 0-23 0 0 22 0 43 0 65-49-49-97-97-145-145-6 7-12 12-17 17 48 48 96 97 144 145 0 0 0 1-1 1z"/>
<glyph glyph-name="ellipsis" unicode="&#57369;" d="M174 232c-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24 0-14-11-24-24-24z m78 0c-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24 0-14-11-24-24-24z m78 0c-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24 0-14-11-24-24-24z"/>
<glyph glyph-name="check" unicode="&#57370;" d="M256 426c95 0 172-77 171-173 0-93-77-169-171-169-95 0-171 77-171 173 0 93 78 169 171 169z m-23-192c-2 3-3 5-4 6-13 13-26 26-39 39-10 11-26 12-36 2-10-10-10-25 1-36 20-21 40-41 60-61 11-10 26-10 36 0 36 35 71 71 106 107 4 3 7 9 8 14 2 10-3 21-13 26-10 4-21 3-29-5-28-29-57-57-85-86-2-1-3-3-5-6z"/>
<glyph glyph-name="sliders" unicode="&#38;" d="M185 371l-35 0c-9 0-17-8-17-16 0-9 8-16 17-16l35 0z m190 0l-99 0 0-32 99 0c9 0 17 7 17 16 0 8-8 16-17 16m-163-195l-62 0c-9 0-17-8-17-17 0-9 8-16 17-16l62 0z m163 0l-71 0 0-33 71 0c9 0 17 7 17 16 0 9-8 17-17 17m-63 95l-162 0c-9 0-17-6-17-15 0-9 8-16 17-16l162 0z m-63 120c2 0 4-1 6-4 2-1 2-4 2-8l0-48c0-4 0-7-2-8-2-3-4-4-6-4l-36 0c-2 0-4 1-6 4-2 2-2 5-2 8l0 48c0 4 0 6 2 8 2 3 4 4 6 4z m126-100c2 0 4-1 6-4 2-2 2-5 2-8l0-48c0-2-1-5-2-8-2-2-4-4-6-4l-34 0c-2 0-4 2-6 4-2 2-3 5-3 8l0 48c0 4 1 7 3 8 2 3 4 4 6 4z m-100-96c2 0 5-1 7-3 2-3 2-6 2-9l0-48c0-2 0-5-2-8-2-3-4-4-7-4l-36 0c-3 0-5 1-6 4-2 2-3 5-3 8l0 48c0 4 1 6 3 9 1 2 4 3 6 3z"/>
<glyph glyph-name="polyline" unicode="&#57371;" d="M150 90c1 0 3 0 5-1 4-1 8 0 12 2 3 1 6 5 7 9 1 3 0 8-2 11-2 3-5 6-9 7-2 1-4 1-5 1-4 1-8 1-12-1-3-2-6-6-7-9-1-4-1-8 2-12 2-3 5-6 9-7z m-55 36c1-2 2-3 3-5 1 0 1-1 2-1 0-1 1-2 2-2 3-3 6-5 10-5 2 0 4 1 6 2 2 0 4 1 5 3 2 3 4 6 4 10 0 4-1 8-4 11-1 1-3 2-4 4 1-1 2-2 3-3-1 0-1 1-1 1-2 3-6 6-9 7-2 1-4 1-6 0-2 0-4 0-6-2-3-2-6-5-7-9-1-4 0-7 2-11z m-15 64c0-3 0-6 0-8 0-4 1-8 4-11 3-3 7-5 11-4 8 0 15 6 15 15 0 2 0 5 0 8 0 4-2 8-5 10-2 3-6 5-10 5-9-1-15-7-15-15z m22 87c-5-10-9-21-12-32-2-8 3-17 10-19 9-1 16 3 19 11 2 8 5 17 9 25 3 7 2 16-6 20-6 4-17 2-20-5z m59 82c-12-12-23-24-33-38-5-6-2-16 5-20 8-5 16-1 20 5 8 11 19 22 29 32 6 5 6 15 0 21-6 6-15 5-21 0z m104-63c-19-8-25-31-20-50 4-21 23-33 43-34 21-2 40 10 52 26 12 18 14 41 8 62-7 20-21 37-40 47-19 9-42 11-63 4-39-12-65-49-69-89-3-38 15-77 45-100 33-27 78-34 119-23 80 23 131 113 109 193-10 37-37 66-72 81-40 16-88 11-128-3-10-3-21-8-31-13-7-3-9-13-5-20 4-7 13-9 20-5 32 15 68 24 104 20 15-1 30-6 42-14 3-1 6-3 9-6 2-1-2 2 0 0 1 0 2-1 2-2 2-1 4-2 5-4 3-2 5-5 7-7 0 0 4-5 2-3 1-1 2-3 3-4 2-3 4-6 6-9 1-2 2-4 2-6 1 0 1-1 1-2-1 3 1-1 1-1 3-8 5-16 6-25 0 2 0-1 0-1 1-1 1-2 1-3 0-2 0-4 0-6 0-5 0-10 0-15-1-7-2-14-4-23-4-16-10-29-20-43-1-2-2-4-4-5 0-1-1-3 0-1-1-1-1-2-2-2-2-3-5-7-8-9-2-3-5-5-8-8-1-1-3-2-4-4 0 0-2-2-1 0-1-1-2-2-3-3-14-10-26-15-41-19-9-2-14-3-23-3-9-1-17 0-23 1-16 3-31 9-43 19-1 1-5 4-7 7-3 2-6 5-8 8 0 1-2 3-1 1 0 0 0 1-1 2-1 1-2 3-3 5-2 3-4 6-6 10 0 1-2 5-1 3-1 1-2 3-2 5-2 4-3 8-3 12-1 1-1 3-1 5-1 2 0-2 0 0 0 1 0 2-1 3 0 4 0 8 0 12 0 2 1 4 1 6 0 3 0 1 0 0 0 1 0 2 0 3 1 4 2 8 3 11 1 2 1 4 2 5 0 0 1 3 0 2 0-2 1 1 1 1 2 4 4 7 6 11 1 1 2 2 3 4-1-2 0 0 0 0 1 1 2 2 2 2 3 3 6 6 9 9 0 0 2 1 0 0 1 1 2 1 3 2 1 1 3 2 5 3 3 2 7 4 10 5 8 3 14 4 22 4 2 0 5 0 7-1-2 1 2 0 3 0 2-1 4-1 5-2 1 0 2-1 3-1-2 1 1 0 1-1 4-1 7-3 10-5-1 1 2-2 3-2 1-2 3-4 4-5 1-1 2-2 2-3 0 1-1 2 1 0 1-2 2-3 3-5 1-2 2-3 3-5 1-3 0-2 0-1 0-1 1-3 1-4 1-2 1-4 2-5 0-1 0-2 0-4 0 2 1 0 1 0 0-3 0-5 0-7 0-1 0-2 0-2 0-1 0-1 0 0 0-1 0-2-1-2 0-2 0-4-1-6 0-1-1-2-1-3-1-2 1 2 0 0-1-2-2-3-3-5 0-1-1-1-1-2-3-4 1 1-1-1-1-1-2-3-3-4-1 0-5-4-3-2-2-1-3-2-5-3 0 0-1-1-2-1-1-1 2 0-1-1-2 0-4-1-6-1 3 1-1 0-2 0-1 0-3 0-4 0 3 0 0 0 0 0-1 1-3 1-4 1 0 0-3 1-1 1 2-1 0 0 0 0-2 1-4 2-5 3 2-2-1 1-2 2 2-2-1 1-1 2-2 2 0 1 0 0 0 1-1 2-1 2 0 1 0 2 0 2-1 2 0-2 0 0 0 1-1 3-1 4 0 1 1 2 1 3 0-3 0-1 0 0 0 1 0 2 1 3 0 0 1 3 0 1-1-2 1 2 2 2-2-2 0 1 1 2-2-2 2 0 3 1 7 3 9 14 5 20-5 8-13 10-21 6z"/>
<glyph glyph-name="source" unicode="&#57372;" d="M397 222c-40 0-73-28-76-67-41 9-57 54-59 60-16 47-46 69-90 69-4 0-7 0-10-1l-3 0c-1 0-1 0-2 0l-2-2c-7-4-27-13-42-13-27 0-49 22-49 49 0 26 22 48 49 48 27 0 49-22 49-48 0-3 0-8 0-8 0-1 0-1 0-2 1-1 2-1 2-1l181-1c1 0 2 1 2 1 1 1 1 2 1 2 0 0 0 6 0 9 0 26 22 48 49 48 27 0 49-22 49-48 0-27-22-49-49-49-11 0-21 4-30 10 0 1-1 1-1 1-1 0-1 0-1 0-1 0-1-1-2-1l-14-19c0-1-1-1-1-2 1-1 1-1 1-2 14-10 31-16 48-16 43 0 78 35 78 77 0 43-35 77-78 77-41 0-74-26-77-65l-131 0c-3 39-36 66-76 66-43 0-78-35-78-77 0-43 35-77 78-77 21 0 45 9 54 15 2 0 3 0 4 0 31 0 51-14 63-49 1-3 26-79 101-79l0 0 12 0c1 0 2 1 2 1 1 1 1 1 1 2 0 0-1 16-1 18 0 26 21 45 48 45 27 0 49-21 49-48 0-26-22-48-49-48-11 0-21 3-30 10 0 0-1 0-1 0 0 0-1 0-1 0 0 0-1 0-2-1l-14-18c0-1-1-2 0-2 0-1 0-2 1-2 13-10 30-16 47-16 43 0 78 35 78 77 0 43-35 77-78 77z"/>
<glyph glyph-name="playback-play" unicode="&#57373;" d="M128 416l256-160-256-160z"/>
<glyph glyph-name="stop-square" unicode="&#57374;" d="M384 128l-256 0 0 256 256 0z"/>
<glyph glyph-name="avatar-t-pose" unicode="&#57375;" d="M274 70c-1 0-1 0-1 0-12-1-14 2-14 12 0 0 0 133 0 135-3 0-6 0-9 0-2-10 1-140 1-140-1-7-2-8-9-8-2 0-5 0-7 0-8 109-16 188-16 188 0 24 0 46 0 66-1-1-33 0-44 1-15 0-62 1-79 1-8 0-14 3-18 10-1 2-2 4-4 6 8 1 15 1 22 1 35 2 99 9 100 13 14 10 23 10 36 10 15 0 31 0 46-1 11-1 24 3 37-10 20-10 81-11 123-11 0 0 1 0 1-1-4-11-12-17-25-16-29-1-77-3-127-1 1-20 1-42 2-66 0 0-6-59-15-189z m-13 372c16-6 14-20 13-32 0-5-1-10-1-14-2-11-10-18-20-18-11 0-19 8-20 18-1 6-1 13-2 20-1 7 3 13 11 15 10 3 10 3 19 11z"/>
<glyph glyph-name="check-1" unicode="&#57376;" d="M233 234c-2 3-3 5-4 6-13 13-26 26-39 39-10 11-26 12-36 2-10-10-10-25 1-36 20-21 40-41 60-61 11-10 26-10 36 0 36 35 71 71 106 107 4 3 7 9 8 14 2 10-3 21-13 26-10 4-21 3-29-5-28-29-57-57-85-86-2-1-3-3-5-6z"/>
<glyph glyph-name="exchange" unicode="&#57377;" d="M315 344c0 8 0 15 0 22 0 7 0 13 1 20 0 2 2 5 3 6 3 1 6 0 8-1 1 0 2-1 2-1 21-21 41-42 62-62 5-5 5-8 0-13-21-21-42-42-62-63-5-4-11-4-13 1-1 1-1 3-1 5 0 11 0 22 0 33 0 2 0 3 0 6-2 0-4 0-5 0-61 0-122 0-183 0-9 0-10 1-10 10 0 10 0 19 0 29 0 6 2 8 8 9 1 0 2 0 4 0 60 0 120 0 180 0 2-1 4-1 6-1z m-116-121c2 0 4 0 6 0 61 0 122 0 182 0 9 0 11-1 11-10 0-10 0-19 0-29 0-6-3-8-9-9-1 0-3 0-4 0-60 0-120 0-180 0-2 0-4 0-6 0 0-2 0-3 0-5 0-11 0-23 0-34 0-4-1-6-4-8-4-1-6 0-9 2-21 22-42 43-64 64-4 4-4 7 1 12 21 21 42 42 63 63 3 3 6 4 9 2 3-1 4-4 4-7 0-12 0-24 0-35 0-2 0-3 0-6z"/>
<glyph glyph-name="hfc" unicode="&#57378;" d="M370 142c-12-23-30-42-51-55-12-8-25-14-39-18-38-12-79-7-115 12-35 19-61 51-72 90-12 38-7 79 12 114 12 23 29 42 50 55 7 5 14 8 22 12l1-229c10-6 22-11 34-14l-1 251c13 2 31 2 50 2l1-254c12 2 24 6 34 11l0 244c37 0 72-1 84-1l7 33c-15 0-52 1-91 1l0 57 166 1 0 34-201-1 1-91c-19 0-37-1-51-2l-1 93-34-1 1-98c-44-15-80-45-102-86-24-44-29-94-15-141 14-47 46-86 89-110 44-23 94-29 141-15 17 6 33 13 48 22 26 17 47 40 62 68 8 14 13 28 17 44-11 2-22 4-33 7-3-12-8-24-14-35z"/>
<glyph glyph-name="home-1" unicode="&#57379;" d="M155 273c-5 0-10 2-14 7-5 7-3 17 5 23l29 19 84 59 109-79c8-5 9-15 4-22-5-8-16-9-23-4l-90 65-65-46-30-19c-3-2-6-3-9-3z m84-78c0 12 10 21 21 21 11 0 21-10 21-21l0-60 75 0 0 89-32 24-64 46-68-46-33-21 0-92 80 0 0 60z"/>
<glyph glyph-name="private-key" unicode="&#57380;" d="M238 263c-30 43-16 96 20 121 34 25 82 21 112-8 30-31 33-79 8-114-25-34-77-48-120-18-21-21-41-41-61-61 2-3 5-6 7-8 6-6 12-12 18-18 3-3 3-5 0-7-3-3-6-6-9-9-3-3-5-3-7 0-5 5-9 9-14 14-5-5-9-10-14-15 4-4 9-9 14-14 2-2 2-4 0-6-4-3-7-7-10-10-3-2-4-2-6 0-14 14-28 28-42 42-4 3-2 5 0 7 33 33 67 66 100 99 1 2 3 3 4 5z m131 51c0 34-26 61-61 61-33 0-60-27-61-60 0-35 27-61 62-62 33 0 60 28 60 61z"/>
<glyph glyph-name="security-pic" unicode="&#57382;" d="M365 406l-212 0c-18 0-33-14-33-32l0-91c9 5 18 11 27 15l0 76c0 3 3 5 6 5l212 0c3 0 5-2 5-5l0-212c0-3-2-6-5-6l-106 0 0-27 106 0c18 0 32 15 32 33l0 212c0 18-14 32-32 32z m-153-209l0 16c0 28-23 51-51 51-28 0-51-23-51-51l0-16c-10-2-18-11-18-22l0-39c0-12 9-22 21-22l95 0c12 0 21 10 21 22l0 39c1 11-7 20-17 22z m-51 44c15 0 27-12 27-28l0-16-55 0 0 16c0 16 13 28 28 28z m183 49l-66 49-83-50c7-4 14-10 19-17l62 37 68-50 0 31z m-114-44l114 0 0-26-114 0z"/>
<glyph glyph-name="wallet" unicode="&#57383;" d="M400 400c-3 10-8 19-15 24-7 5-15 8-24 7l-227 0c-19 0-35-15-35-34l0-58c-19-2-34-18-34-37l0-95c0-19 15-35 34-37l0-53c0-19 16-34 35-34l228 0c20 0 30 16 38 31l0 1c1 2 21 53 21 139 0 83-19 140-21 146z m-309-193l0 95c0 6 5 11 11 11l86 0c6 0 11-5 11-11l0-95c0-6-5-11-11-11l-86 0c-6 0-11 5-11 11z m285-82c-8-16-11-16-14-16l-228 0c-5 0-9 4-9 8l0 53 63 0c21 0 37 17 37 37l0 95c0 20-16 37-37 37l-63 0 0 58c0 4 4 8 9 8l228 0c3 0 10 1 13-13l0 0 0-1c1 0 20-55 20-137 0-77-17-124-19-129z"/>
<glyph glyph-name="send" unicode="&#57384;" d="M391 376c4-4 4-10 2-16-7-21-14-42-22-63-21-63-43-125-65-188-1-4-4-7-6-10-8-6-17-4-22 5-15 28-30 56-44 85-1 1 0 3 0 4 18 21 35 43 53 64 5 6 10 12 15 18 4 6 5 10 1 14-4 4-8 3-14-1-18-15-36-30-54-44-9-8-19-16-28-24-1-1-3-1-4 0-29 14-57 29-85 44-6 3-8 8-8 14 1 7 5 11 11 13 36 13 72 25 107 37 49 17 97 34 145 51 7 2 13 2 18-3z"/>
<glyph glyph-name="password" unicode="&#57385;" d="M104 267l0 41 22 0 0-41 35 20 11-19-35-20 35-20-11-20-35 20 0-40-22 0 0 40-35-20-11 20 35 20-35 20 11 19z m136 0l0 41 23 0 0-41 35 20 11-19-35-20 35-20-11-20-35 20 0-40-23 0 0 40-35-20-11 20 35 20-35 20 11 19z m137 0l0 41 23 0 0-41 34 20 12-19-35-20 35-20-12-20-35 20 0-40-22 0 0 40-35-20-11 20 35 20-35 20 11 19z"/>
<glyph glyph-name="rez" unicode="&#57381;" d="M373 321c-2 5-6 8-11 8l-49 8 55 61c4 4 5 9 3 14-2 5-7 8-12 8 0 0 0 0 0 0l-114-1c-5-1-10-4-12-9l-54-136c-1-4-1-8 1-11 2-4 6-6 9-7l38-5-54-136c-2-6 0-13 6-16 2-1 4-2 7-2 3 0 7 2 10 5l175 206c3 4 3 9 2 13z"/>
<glyph glyph-name="keyboard-collapse" unicode="&#57387;" d="M373 249l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m224-1l18 0c7 0 13 6 13 13 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-7 6-13 13-13l17 0m252 39l-31 0 0 25 31 0z m-42 0l-31 0 0 25 31 0z m-41 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-41 0l-32 0 0 25 32 0z m218-1l18 0c7 0 13 6 13 13 0 8-6 14-13 14l-18 0m-262 0l-17 0c-7 0-13-6-13-14 0-7 6-13 13-13l17 0m288-124l-315 0c-33 0-59 28-59 61l0 76c0 34 26 61 59 61l315 0c33 0 59-27 59-61l0-76c1-33-26-61-59-61z m-315 172c-18 0-33-16-33-34l0-77c0-19 15-34 33-34l315 0c18 0 33 15 33 34l0 77c0 19-15 34-33 34z m248-99l32 0 0-25-32 0z m-41 0l31 0 0-25-31 0z m-42 0l31 0 0-25-31 0z m-43 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-41 0l31 0 0-25-31 0z m250-26l18 0c7 0 13 6 13 14 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-8 6-13 13-13l17 0m81-82l50-50 53 54-107 0"/>
<glyph glyph-name="image" unicode="&#57386;" d="M257 428c52 0 104 0 156 0 24 0 37-13 37-37 1-90 1-179 0-269 0-25-13-38-39-38-103 0-207 0-311 0-26 0-39 13-39 40 0 88 0 176 0 263 0 28 13 41 41 41 51 0 103 0 155 0z m167-263c0 7 0 10 0 14 0 69 0 138 0 206 0 17 0 17-17 17-101 0-202 0-303 0-16 0-17-1-17-17 0-58 0-115 0-173 0-3 0-7 0-12 8 3 14 6 19 9 17 8 30 7 44-6 5-5 10-10 15-15 5-7 11-8 19-4 40 21 81 41 121 61 19 10 31 8 46-7 9-9 18-18 27-27 15-15 29-29 46-46z m-328-54c7 0 11-1 15-1 98 0 197 0 296 0 6 0 14 2 16 6 5 7 0 14-6 20-27 26-54 53-80 80-8 9-15 9-26 4-67-35-135-68-203-102-3-2-6-4-12-7z m-8 26c21 10 40 20 63 31-8 7-14 12-20 17-2 2-7 3-10 3-30-9-36-17-34-48 0 0 0-1 1-3z m134 169c1-25-21-46-46-46-25-1-46 20-47 46 0 25 21 46 47 47 25 0 46-21 46-47z m-46 22c-12 0-22-9-22-21 0-13 9-22 21-23 13 0 23 9 23 22 0 13-10 22-22 22z"/>
</font></defs></svg>

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -12,7 +12,7 @@
<body>
<div class="container">
<h1>HiFi Glyphs</h1>
<p class="small">This font was created in<a href="http://highfidelity.com/">High Fidelity</a></p>
<p class="small">This font was created for use in<a href="http://highfidelity.io/">High Fidelity</a></p>
<h2>CSS mapping</h2>
<ul class="glyphs css-mapping">
<li>
@ -520,8 +520,52 @@
<input type="text" readonly="readonly" value="avatar-t-pose">
</li>
<li>
<div class="icon icon-check-2-01"></div>
<input type="text" readonly="readonly" value="check-2-01">
<div class="icon icon-check-1"></div>
<input type="text" readonly="readonly" value="check-1">
</li>
<li>
<div class="icon icon-exchange"></div>
<input type="text" readonly="readonly" value="exchange">
</li>
<li>
<div class="icon icon-hfc"></div>
<input type="text" readonly="readonly" value="hfc">
</li>
<li>
<div class="icon icon-home-1"></div>
<input type="text" readonly="readonly" value="home-1">
</li>
<li>
<div class="icon icon-private-key"></div>
<input type="text" readonly="readonly" value="private-key">
</li>
<li>
<div class="icon icon-security-pic"></div>
<input type="text" readonly="readonly" value="security-pic">
</li>
<li>
<div class="icon icon-wallet"></div>
<input type="text" readonly="readonly" value="wallet">
</li>
<li>
<div class="icon icon-send"></div>
<input type="text" readonly="readonly" value="send">
</li>
<li>
<div class="icon icon-password"></div>
<input type="text" readonly="readonly" value="password">
</li>
<li>
<div class="icon icon-rez"></div>
<input type="text" readonly="readonly" value="rez">
</li>
<li>
<div class="icon icon-keyboard-collapse"></div>
<input type="text" readonly="readonly" value="keyboard-collapse">
</li>
<li>
<div class="icon icon-image"></div>
<input type="text" readonly="readonly" value="image">
</li>
</ul>
<h2>Character mapping</h2>
@ -1034,6 +1078,50 @@
<div data-icon="&#xe020;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe020;">
</li>
<li>
<div data-icon="&#xe021;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe021;">
</li>
<li>
<div data-icon="&#xe022;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe022;">
</li>
<li>
<div data-icon="&#xe023;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe023;">
</li>
<li>
<div data-icon="&#xe024;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe024;">
</li>
<li>
<div data-icon="&#xe026;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe026;">
</li>
<li>
<div data-icon="&#xe027;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe027;">
</li>
<li>
<div data-icon="&#xe028;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe028;">
</li>
<li>
<div data-icon="&#xe029;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe029;">
</li>
<li>
<div data-icon="&#xe025;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe025;">
</li>
<li>
<div data-icon="&#xe02b;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02b;">
</li>
<li>
<div data-icon="&#xe02a;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02a;">
</li>
</ul>
</div>
<script>(function() {

View file

@ -416,6 +416,39 @@
.icon-avatar-t-pose:before {
content: "\e01f";
}
.icon-check-2-01:before {
.icon-check-1:before {
content: "\e020";
}
.icon-exchange:before {
content: "\e021";
}
.icon-hfc:before {
content: "\e022";
}
.icon-home-1:before {
content: "\e023";
}
.icon-private-key:before {
content: "\e024";
}
.icon-security-pic:before {
content: "\e026";
}
.icon-wallet:before {
content: "\e027";
}
.icon-send:before {
content: "\e028";
}
.icon-password:before {
content: "\e029";
}
.icon-rez:before {
content: "\e025";
}
.icon-keyboard-collapse:before {
content: "\e02b";
}
.icon-image:before {
content: "\e02a";
}

View file

@ -342,7 +342,7 @@
<p>In High Fidelity, your private keys are used to securely access the contents of your Wallet and Purchases.</p>
<hr>
<h3>Where are my private keys stored?"</h3>
<h3>Where are my private keys stored?</h3>
<p>By default, your private keys are only stored on your hard drive in High Fidelity Interface's <code>AppData</code> directory.</p>
<p>Here is the file path of your hifikey - you can browse to it using your file explorer.</p>
<div class="alert"> <code>HIFIKEY_PATH_REPLACEME</code> </div>

View file

@ -0,0 +1,9 @@
<?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="M348.5,162.5c1.7,0,3,1.4,3,3v181.4c0,1.7-1.3,3-3,3H167.2c-1.6,0-3-1.3-3-3V165.5c0-1.6,1.4-3,3-3H348.5 M348.5,145.5
H167.2c-11,0-20,9-20,20v181.4c0,11,9,20,20,20h181.4c11,0,20-9,20-20V165.5C368.5,154.5,359.6,145.5,348.5,145.5L348.5,145.5z"/>
<rect x="161.6" y="253.6" width="96.3" height="96.3"/>
<rect x="256.1" y="159.8" width="95.4" height="95.4"/>
</svg>

After

Width:  |  Height:  |  Size: 717 B

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, 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:#FFFFFF;}
</style>
<g>
<path class="st0" d="M256.5,83.7c52.1,0,104.2-0.1,156.3,0.1c23.7,0.1,37.6,13.5,37.6,37.2c0.2,89.6,0.3,179.2,0,268.7
c-0.1,25.5-13.6,38.2-39,38.3c-103.8,0.1-207.5,0.1-311.3,0c-25.8,0-38.8-13.5-38.8-39.7c0-87.9,0-175.9,0-263.8
c0-27.7,12.8-40.7,40.2-40.8C153.2,83.6,204.8,83.7,256.5,83.7z M423.7,347c0.4-6.9,0.7-10.5,0.7-14.1c0-68.8,0-137.5,0-206.3
c0-16.2-0.6-16.7-17-16.7c-101.3,0-202.5,0-303.8,0c-15.9,0-16.9,1-16.9,17.2c0,57.5,0,115,0,172.5c0,3.5,0.3,7.1,0.6,12.4
c7.3-3.5,13.2-6.3,19.1-9.2c16.7-8.1,29.8-6.4,43.6,6c5.2,4.7,10.3,9.8,14.7,15.3c5.6,6.9,10.8,7.8,19.1,3.5
c40.3-20.8,81-40.9,121.6-61.1c18.3-9.1,30.6-7.1,45.3,7c9.3,9,18.3,18.2,27.4,27.4C392.7,315.5,407.2,330.2,423.7,347z
M95.6,400.9c7.3,0.4,11.2,0.8,15.1,0.8c98.7,0,197.5,0.1,296.2-0.2c5.6,0,14-1.4,16.2-5.1c4.5-7.4-0.3-14.4-6.5-20.5
c-26.8-26.2-53.5-52.6-79.5-79.6c-8.5-8.9-15.4-9.4-26.1-4c-67.5,34.3-135.3,67.9-203,101.8C104.9,395.7,101.8,397.5,95.6,400.9z
M88.2,375.1c20.6-10.3,40.2-20.1,62.4-31.3c-7.6-6.7-13.2-12.2-19.5-16.9c-2.6-1.9-7-3.3-9.9-2.5c-30,8.1-36,16.8-34,47.6
C87.1,372.4,87.4,372.8,88.2,375.1z"/>
<path class="st0" d="M222.2,205.5c0.3,25.1-20.8,46.5-46,46.8c-25.1,0.3-46.5-20.8-46.8-46.1c-0.3-25.1,20.8-46.5,46.1-46.8
C200.5,159.2,221.9,180.2,222.2,205.5z M176,184.1c-12.3-0.1-21.7,9-21.9,21.3c-0.2,12.3,8.8,21.8,21,22.2
c12.8,0.4,22.5-9.1,22.5-21.9C197.5,193.4,188.3,184.2,176,184.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -202,8 +202,4 @@ TreeView {
}
onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
onClicked: {
selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect);
}
}

View file

@ -37,7 +37,7 @@ Windows.ScrollingWindow {
property var assetProxyModel: Assets.proxyModel;
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItems: treeView.selection.selectedIndexes.length;
property var selectedItemCount: treeView.selection.selectedIndexes.length;
Settings {
category: "Overlay.AssetServer"
@ -75,17 +75,17 @@ Windows.ScrollingWindow {
});
}
function doDeleteFile(path) {
console.log("Deleting " + path);
function doDeleteFile(paths) {
console.log("Deleting " + paths);
Assets.deleteMappings(path, function(err) {
Assets.deleteMappings(paths, function(err) {
if (err) {
console.log("Asset browser - error deleting path: ", path, err);
console.log("Asset browser - error deleting paths: ", paths, err);
box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err);
box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err);
box.selected.connect(reload);
} else {
console.log("Asset browser - finished deleting path: ", path);
console.log("Asset browser - finished deleting paths: ", paths);
reload();
}
});
@ -143,9 +143,9 @@ Windows.ScrollingWindow {
}
function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
if (selectedItems > 1) {
if (selectedItemCount > 1) {
return false;
}
@ -155,7 +155,7 @@ Windows.ScrollingWindow {
}
function canRename() {
if (treeView.selection.hasSelection && selectedItems == 1) {
if (treeView.selection.hasSelection && selectedItemCount == 1) {
return true;
} else {
return false;
@ -181,92 +181,103 @@ Windows.ScrollingWindow {
return;
}
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
var SHAPE_TYPE_STATIC_MESH = 3;
var SHAPE_TYPE_BOX = 4;
var SHAPE_TYPE_SPHERE = 5;
if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
var textures = JSON.stringify({ "tex.picture": defaultURL});
var shapeType = "box";
var dynamic = false;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity);
} else {
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
var SHAPE_TYPE_STATIC_MESH = 3;
var SHAPE_TYPE_BOX = 4;
var SHAPE_TYPE_SPHERE = 5;
var SHAPE_TYPES = [];
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
var SHAPE_TYPES = [];
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
var DYNAMIC_DEFAULT = false;
var prompt = desktop.customInputDialog({
textInput: {
label: "Model URL",
text: defaultURL
},
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 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
}
});
prompt.selected.connect(function (jsonResult) {
if (jsonResult) {
var result = JSON.parse(jsonResult);
var url = result.textInput.trim();
var shapeType;
switch (result.comboBox) {
case SHAPE_TYPE_SIMPLE_HULL:
shapeType = "simple-hull";
break;
case SHAPE_TYPE_SIMPLE_COMPOUND:
shapeType = "simple-compound";
break;
case SHAPE_TYPE_STATIC_MESH:
shapeType = "static-mesh";
break;
case SHAPE_TYPE_BOX:
shapeType = "box";
break;
case SHAPE_TYPE_SPHERE:
shapeType = "sphere";
break;
default:
shapeType = "none";
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
var DYNAMIC_DEFAULT = false;
var prompt = desktop.customInputDialog({
textInput: {
label: "Model URL",
text: defaultURL
},
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 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
}
});
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
if (shapeType === "static-mesh" && dynamic) {
// The prompt should prevent this case
print("Error: model cannot be both static mesh and dynamic. This should never happen.");
} else if (url) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity;
if (dynamic) {
// Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a
// different scripting engine from QTScript.
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10);
} else {
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
prompt.selected.connect(function (jsonResult) {
if (jsonResult) {
var result = JSON.parse(jsonResult);
var url = result.textInput.trim();
var shapeType;
switch (result.comboBox) {
case SHAPE_TYPE_SIMPLE_HULL:
shapeType = "simple-hull";
break;
case SHAPE_TYPE_SIMPLE_COMPOUND:
shapeType = "simple-compound";
break;
case SHAPE_TYPE_STATIC_MESH:
shapeType = "static-mesh";
break;
case SHAPE_TYPE_BOX:
shapeType = "box";
break;
case SHAPE_TYPE_SPHERE:
shapeType = "sphere";
break;
default:
shapeType = "none";
}
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
if (shapeType === "static-mesh" && dynamic) {
// The prompt should prevent this case
print("Error: model cannot be both static mesh and dynamic. This should never happen.");
} else if (url) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity;
if (dynamic) {
// Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a
// different scripting engine from QTScript.
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10);
} else {
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
}
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity);
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity);
}
}
}
});
});
}
}
function copyURLToClipboard(index) {
@ -333,29 +344,28 @@ Windows.ScrollingWindow {
});
}
function deleteFile(index) {
var path = [];
var paths = [];
if (!index) {
for (var i = 0; i < selectedItems; i++) {
treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100);
index = treeView.selection.currentIndex;
path[i] = assetProxyModel.data(index, 0x100);
for (var i = 0; i < selectedItemCount; ++i) {
index = treeView.selection.selectedIndexes[i];
paths[i] = assetProxyModel.data(index, 0x100);
}
}
if (!path) {
if (!paths) {
return;
}
var modalMessage = "";
var items = selectedItems.toString();
var items = selectedItemCount.toString();
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
var typeString = isFolder ? 'folder' : 'file';
if (selectedItems > 1) {
if (selectedItemCount > 1) {
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
} else {
modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?";
modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?";
}
var object = desktop.messageBox({
@ -367,7 +377,7 @@ Windows.ScrollingWindow {
});
object.selected.connect(function(button) {
if (button === OriginalDialogs.StandardButton.Yes) {
doDeleteFile(path);
doDeleteFile(paths);
}
});
}
@ -694,7 +704,7 @@ Windows.ScrollingWindow {
}
}
}
}
}// End_OF( itemLoader )
Rectangle {
id: treeLabelToolTip
@ -731,50 +741,59 @@ Windows.ScrollingWindow {
showTimer.stop();
treeLabelToolTip.visible = false;
}
}
}// End_OF( treeLabelToolTip )
MouseArea {
propagateComposedEvents: true
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (!HMD.active) { // Popup only displays properly on desktop
var index = treeView.indexAt(mouse.x, mouse.y);
treeView.selection.setCurrentIndex(index, 0x0002);
contextMenu.currentIndex = index;
contextMenu.popup();
if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop
// Only display the popup if the click triggered within
// the selection.
var clickedIndex = treeView.indexAt(mouse.x, mouse.y);
var displayContextMenu = false;
for ( var i = 0; i < selectedItemCount; ++i) {
var currentSelectedIndex = treeView.selection.selectedIndexes[i];
if (clickedIndex === currentSelectedIndex) {
contextMenu.popup();
break;
}
}
}
}
}
Menu {
id: contextMenu
title: "Edit"
property var url: ""
property var currentIndex: null
MenuItem {
text: "Copy URL"
enabled: (selectedItemCount == 1)
onTriggered: {
copyURLToClipboard(contextMenu.currentIndex);
copyURLToClipboard(treeView.selection.currentIndex);
}
}
MenuItem {
text: "Rename"
enabled: (selectedItemCount == 1)
onTriggered: {
renameFile(contextMenu.currentIndex);
renameFile(treeView.selection.currentIndex);
}
}
MenuItem {
text: "Delete"
enabled: (selectedItemCount > 0)
onTriggered: {
deleteFile(contextMenu.currentIndex);
deleteFile();
}
}
}
}
}// End_OF( contextMenu )
}// End_OF( treeView )
Row {
id: infoRow
@ -787,8 +806,8 @@ Windows.ScrollingWindow {
function makeText() {
var numPendingBakes = assetMappingsModel.numPendingBakes;
if (selectedItems > 1 || numPendingBakes === 0) {
return selectedItems + " items selected";
if (selectedItemCount > 1 || numPendingBakes === 0) {
return selectedItemCount + " items selected";
} else {
return numPendingBakes + " bakes pending"
}
@ -885,7 +904,7 @@ Windows.ScrollingWindow {
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
}
}
}
}// End_OF( infoRow )
HifiControls.ContentSection {
id: uploadSection
@ -945,7 +964,7 @@ Windows.ScrollingWindow {
}
}
}
}
}// End_OF( uploadSection )
}
}

View file

@ -48,7 +48,7 @@ Rectangle {
// The letterbox used for popup messages
LetterboxMessage {
id: letterboxMessage;
z: 999; // Force the popup on top of everything else
z: 998; // Force the popup on top of everything else
}
Connections {
target: GlobalServices
@ -60,7 +60,7 @@ Rectangle {
// The ComboDialog used for setting availability
ComboDialog {
id: comboDialog;
z: 999; // Force the ComboDialog on top of everything else
z: 998; // Force the ComboDialog on top of everything else
dialogWidth: parent.width - 50;
dialogHeight: parent.height - 100;
}
@ -1013,7 +1013,7 @@ Rectangle {
}
MouseArea {
anchors.fill: parent;
enabled: myData.userName !== "Unknown user";
enabled: myData.userName !== "Unknown user" && !userInfoViewer.visible;
hoverEnabled: true;
onClicked: {
popupComboDialog("Set your availability:",
@ -1044,6 +1044,7 @@ Rectangle {
HifiControls.TabletWebView {
id: userInfoViewer;
z: 999;
anchors {
top: parent.top;
bottom: parent.bottom;

View file

@ -75,8 +75,6 @@ Item {
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
passphraseField.error = false;
passphraseField.focus = true;
sendSignalToParent({method: 'disableHmdPreview'});
} else {
sendSignalToParent({method: 'maybeEnableHmdPreview'});
@ -206,6 +204,14 @@ Item {
placeholderText: "passphrase";
activeFocusOnPress: true;
activeFocusOnTab: true;
onVisibleChanged: {
if (visible) {
error = false;
focus = true;
forceActiveFocus();
}
}
onFocusChanged: {
root.keyboardRaised = focus;

View file

@ -391,7 +391,7 @@ Item {
anchors.topMargin: 15;
width: 118;
height: paintedHeight;
wrapMode: Text.WordWrap;
wrapMode: Text.Wrap;
// Alignment
horizontalAlignment: Text.AlignRight;
}
@ -408,7 +408,7 @@ Item {
height: paintedHeight;
color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight;
linkColor: hifi.colors.blueAccent;
wrapMode: Text.WordWrap;
wrapMode: Text.Wrap;
font.strikeout: model.status === "invalidated";
onLinkActivated: {

View file

@ -923,7 +923,7 @@ Item {
anchors.leftMargin: 20;
anchors.right: parent.right;
anchors.rightMargin: 20;
height: 140;
height: 95;
FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; }
TextArea {
@ -947,8 +947,14 @@ Item {
wrapMode: TextEdit.Wrap;
activeFocusOnPress: true;
activeFocusOnTab: true;
// Workaround for no max length on TextAreas
onTextChanged: {
// Don't allow tabs or newlines
if ((/[\n\r\t]/g).test(text)) {
var cursor = cursorPosition;
text = text.replace(/[\n\r\t]/g, '');
cursorPosition = cursor-1;
}
// Workaround for no max length on TextAreas
if (text.length > maximumLength) {
var cursor = cursorPosition;
text = previousText;

View file

@ -39,7 +39,7 @@ Rectangle {
property var assetProxyModel: Assets.proxyModel;
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItems: treeView.selection.selectedIndexes.length;
property var selectedItemCount: treeView.selection.selectedIndexes.length;
Settings {
category: "Overlay.AssetServer"
@ -76,17 +76,17 @@ Rectangle {
});
}
function doDeleteFile(path) {
console.log("Deleting " + path);
function doDeleteFile(paths) {
console.log("Deleting " + paths);
Assets.deleteMappings(path, function(err) {
Assets.deleteMappings(paths, function(err) {
if (err) {
console.log("Asset browser - error deleting path: ", path, err);
console.log("Asset browser - error deleting paths: ", paths, err);
box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err);
box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err);
box.selected.connect(reload);
} else {
console.log("Asset browser - finished deleting path: ", path);
console.log("Asset browser - finished deleting paths: ", paths);
reload();
}
});
@ -146,7 +146,7 @@ Rectangle {
function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
if (selectedItems > 1) {
if (selectedItemCount > 1) {
return false;
}
@ -156,7 +156,7 @@ Rectangle {
}
function canRename() {
if (treeView.selection.hasSelection && selectedItems == 1) {
if (treeView.selection.hasSelection && selectedItemCount == 1) {
return true;
} else {
return false;
@ -182,92 +182,103 @@ Rectangle {
return;
}
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
var SHAPE_TYPE_STATIC_MESH = 3;
var SHAPE_TYPE_BOX = 4;
var SHAPE_TYPE_SPHERE = 5;
if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
var textures = JSON.stringify({ "tex.picture": defaultURL});
var shapeType = "box";
var dynamic = false;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity);
} else {
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
var SHAPE_TYPE_STATIC_MESH = 3;
var SHAPE_TYPE_BOX = 4;
var SHAPE_TYPE_SPHERE = 5;
var SHAPE_TYPES = [];
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
var SHAPE_TYPES = [];ww
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
var DYNAMIC_DEFAULT = false;
var prompt = tabletRoot.customInputDialog({
textInput: {
label: "Model URL",
text: defaultURL
},
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 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
}
});
prompt.selected.connect(function (jsonResult) {
if (jsonResult) {
var result = JSON.parse(jsonResult);
var url = result.textInput.trim();
var shapeType;
switch (result.comboBox) {
case SHAPE_TYPE_SIMPLE_HULL:
shapeType = "simple-hull";
break;
case SHAPE_TYPE_SIMPLE_COMPOUND:
shapeType = "simple-compound";
break;
case SHAPE_TYPE_STATIC_MESH:
shapeType = "static-mesh";
break;
case SHAPE_TYPE_BOX:
shapeType = "box";
break;
case SHAPE_TYPE_SPHERE:
shapeType = "sphere";
break;
default:
shapeType = "none";
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
var DYNAMIC_DEFAULT = false;
var prompt = tabletRoot.customInputDialog({
textInput: {
label: "Model URL",
text: defaultURL
},
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 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
}
});
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
if (shapeType === "static-mesh" && dynamic) {
// The prompt should prevent this case
print("Error: model cannot be both static mesh and dynamic. This should never happen.");
} else if (url) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity;
if (dynamic) {
// Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a
// different scripting engine from QTScript.
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10);
} else {
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
prompt.selected.connect(function (jsonResult) {
if (jsonResult) {
var result = JSON.parse(jsonResult);
var url = result.textInput.trim();
var shapeType;
switch (result.comboBox) {
case SHAPE_TYPE_SIMPLE_HULL:
shapeType = "simple-hull";
break;
case SHAPE_TYPE_SIMPLE_COMPOUND:
shapeType = "simple-compound";
break;
case SHAPE_TYPE_STATIC_MESH:
shapeType = "static-mesh";
break;
case SHAPE_TYPE_BOX:
shapeType = "box";
break;
case SHAPE_TYPE_SPHERE:
shapeType = "sphere";
break;
default:
shapeType = "none";
}
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
if (shapeType === "static-mesh" && dynamic) {
// The prompt should prevent this case
print("Error: model cannot be both static mesh and dynamic. This should never happen.");
} else if (url) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity;
if (dynamic) {
// Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a
// different scripting engine from QTScript.
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10);
} else {
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
}
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity);
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity);
}
}
}
});
});
}
}
function copyURLToClipboard(index) {
@ -334,29 +345,28 @@ Rectangle {
});
}
function deleteFile(index) {
var path = [];
var paths = [];
if (!index) {
for (var i = 0; i < selectedItems; i++) {
treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100);
index = treeView.selection.currentIndex;
path[i] = assetProxyModel.data(index, 0x100);
for (var i = 0; i < selectedItemCount; ++i) {
index = treeView.selection.selectedIndexes[i];
paths[i] = assetProxyModel.data(index, 0x100);
}
}
if (!path) {
if (!paths) {
return;
}
var modalMessage = "";
var items = selectedItems.toString();
var items = selectedItemCount.toString();
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
var typeString = isFolder ? 'folder' : 'file';
if (selectedItems > 1) {
if (selectedItemCount > 1) {
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
} else {
modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?";
modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?";
}
var object = tabletRoot.messageBox({
@ -368,7 +378,7 @@ Rectangle {
});
object.selected.connect(function(button) {
if (button === OriginalDialogs.StandardButton.Yes) {
doDeleteFile(path);
doDeleteFile(paths);
}
});
}
@ -693,7 +703,7 @@ Rectangle {
}
}
}
}
}// End_OF( itemLoader )
Rectangle {
id: treeLabelToolTip
@ -730,50 +740,59 @@ Rectangle {
showTimer.stop();
treeLabelToolTip.visible = false;
}
}
}// End_OF( treeLabelToolTip )
MouseArea {
propagateComposedEvents: true
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (!HMD.active) { // Popup only displays properly on desktop
var index = treeView.indexAt(mouse.x, mouse.y);
treeView.selection.setCurrentIndex(index, 0x0002);
contextMenu.currentIndex = index;
contextMenu.popup();
if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop
// Only display the popup if the click triggered within
// the selection.
var clickedIndex = treeView.indexAt(mouse.x, mouse.y);
var displayContextMenu = false;
for ( var i = 0; i < selectedItemCount; ++i) {
var currentSelectedIndex = treeView.selection.selectedIndexes[i];
if (clickedIndex === currentSelectedIndex) {
contextMenu.popup();
break;
}
}
}
}
}
Menu {
id: contextMenu
title: "Edit"
property var url: ""
property var currentIndex: null
MenuItem {
text: "Copy URL"
enabled: (selectedItemCount == 1)
onTriggered: {
copyURLToClipboard(contextMenu.currentIndex);
copyURLToClipboard(treeView.selection.currentIndex);
}
}
MenuItem {
text: "Rename"
enabled: (selectedItemCount == 1)
onTriggered: {
renameFile(contextMenu.currentIndex);
renameFile(treeView.selection.currentIndex);
}
}
MenuItem {
text: "Delete"
enabled: (selectedItemCount > 0)
onTriggered: {
deleteFile(contextMenu.currentIndex);
deleteFile();
}
}
}
}
}// End_OF( contextMenu )
}// End_OF( treeView )
Row {
id: infoRow
@ -786,8 +805,8 @@ Rectangle {
function makeText() {
var numPendingBakes = assetMappingsModel.numPendingBakes;
if (selectedItems > 1 || numPendingBakes === 0) {
return selectedItems + " items selected";
if (selectedItemCount > 1 || numPendingBakes === 0) {
return selectedItemCount + " items selected";
} else {
return numPendingBakes + " bakes pending"
}
@ -884,7 +903,7 @@ Rectangle {
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
}
}
}
}// End_OF( infoRow )
HifiControls.TabletContentSection {
id: uploadSection
@ -961,7 +980,7 @@ Rectangle {
}
}
}
}
}// End_OF( uploadSection )
}
}

View file

@ -101,6 +101,17 @@ TabView {
}
}
NewEntityButton {
icon: "icons/create-icons/image.svg"
text: "IMAGE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newImageButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/25-web-1-01.svg"
text: "WEB"
@ -133,6 +144,17 @@ TabView {
editTabView.currentIndex = 4
}
}
NewEntityButton {
icon: "icons/create-icons/126-material-01.svg"
text: "MATERIAL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" }
});
editTabView.currentIndex = 2
}
}
}
HifiControls.Button {

View file

@ -0,0 +1,174 @@
//
// NewMaterialDialog.qml
// qml/hifi
//
// Created by Sam Gondelman on 1/17/18
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../../styles-uit"
import "../../controls-uit"
import "../dialogs"
Rectangle {
id: newMaterialDialog
// width: parent.width
// height: parent.height
HifiConstants { id: hifi }
color: hifi.colors.baseGray;
signal sendToScript(var message);
property bool keyboardEnabled: false
property bool punctuationMode: false
property bool keyboardRasied: false
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
}
Item {
id: column1
anchors.rightMargin: 10
anchors.leftMargin: 10
anchors.bottomMargin: 10
anchors.topMargin: 10
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: keyboard.top
Text {
id: text1
text: qsTr("Material URL")
color: "#ffffff"
font.pixelSize: 12
}
TextInput {
id: materialURL
height: 20
text: qsTr("")
color: "white"
anchors.top: text1.bottom
anchors.topMargin: 5
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
font.pixelSize: 12
onAccepted: {
newMaterialDialog.keyboardEnabled = false;
}
MouseArea {
anchors.fill: parent
onClicked: {
newMaterialDialog.keyboardEnabled = HMD.active
parent.focus = true;
parent.forceActiveFocus();
materialURL.cursorPosition = materialURL.positionAt(mouseX, mouseY, TextInput.CursorBetweenCharaters);
}
}
}
Rectangle {
id: textInputBox
color: "white"
anchors.fill: materialURL
opacity: 0.1
}
Row {
id: row1
height: 400
spacing: 30
anchors.top: materialURL.bottom
anchors.topMargin: 5
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
Column {
id: column3
height: 400
spacing: 10
/*Text {
id: text3
text: qsTr("Material Mode")
color: "#ffffff"
font.pixelSize: 12
}
ComboBox {
id: materialMappingMode
property var materialArray: ["UV space material",
"3D projected material"]
width: 200
z: 100
transformOrigin: Item.Center
model: materialArray
}*/
Row {
id: row3
width: 200
height: 400
spacing: 5
anchors.horizontalCenter: column3.horizontalCenter
anchors.horizontalCenterOffset: 0
Button {
id: button1
text: qsTr("Add")
z: -1
onClicked: {
newMaterialDialog.sendToScript({
method: "newMaterialDialogAdd",
params: {
textInput: materialURL.text,
//comboBox: materialMappingMode.currentIndex
}
});
}
}
Button {
id: button2
z: -1
text: qsTr("Cancel")
onClicked: {
newMaterialDialog.sendToScript({method: "newMaterialDialogCancel"})
}
}
}
}
}
}
Keyboard {
id: keyboard
raised: parent.keyboardEnabled
numeric: parent.punctuationMode
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
}

View file

@ -130,6 +130,7 @@ Item {
flickableDirection: Flickable.AutoFlickIfNeeded
keyNavigationEnabled: false
highlightFollowsCurrentItem: false
interactive: false
property int previousGridIndex: -1

View file

@ -18,7 +18,7 @@ Item {
signal showDesktop();
property bool shown: true
property int currentApp: -1;
property alias tabletApps: tabletApps
property alias tabletApps: tabletApps
function setOption(value) {
option = value;
@ -44,7 +44,7 @@ Item {
Component { id: fileDialogBuilder; TabletFileDialog { } }
function fileDialog(properties) {
openModal = fileDialogBuilder.createObject(tabletRoot, properties);
return openModal;
return openModal;
}
Component { id: assetDialogBuilder; TabletAssetDialog { } }
@ -66,6 +66,16 @@ Item {
return false;
}
function isUrlLoaded(url) {
if (currentApp >= 0) {
var currentAppUrl = tabletApps.get(currentApp).appUrl;
if (currentAppUrl === url) {
return true;
}
}
return false;
}
function loadSource(url) {
tabletApps.clear();
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
@ -73,23 +83,27 @@ Item {
}
function loadQMLOnTop(url) {
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.load(tabletApps.get(currentApp).appUrl, function(){
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
})
if (!isUrlLoaded(url)) {
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.load(tabletApps.get(currentApp).appUrl, function(){
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
});
}
}
function loadWebContent(source, url, injectJavaScriptUrl) {
tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url});
loader.load(source, function() {
loader.item.scriptURL = injectJavaScriptUrl;
loader.item.url = url;
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
});
if (!isUrlLoaded(url)) {
tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url});
loader.load(source, function() {
loader.item.scriptURL = injectJavaScriptUrl;
loader.item.url = url;
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
});
}
}
function loadWebBase(url, injectJavaScriptUrl) {
@ -99,7 +113,7 @@ Item {
function loadTabletWebBase(url, injectJavaScriptUrl) {
loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl);
}
function returnToPreviousApp() {
tabletApps.remove(currentApp);
var isWebPage = tabletApps.get(currentApp).isWebUrl;
@ -190,19 +204,19 @@ Item {
property string source: "";
property var item: null;
signal loaded;
onWidthChanged: {
if (loader.item) {
loader.item.width = loader.width;
}
}
onHeightChanged: {
if (loader.item) {
loader.item.height = loader.height;
}
}
function load(newSource, callback) {
if (loader.source == newSource) {
loader.loaded();
@ -226,18 +240,18 @@ Item {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
}
if (callback) {
callback();
}

Binary file not shown.

View file

@ -89,6 +89,15 @@ Fadable {
window.shown = value;
}
// used to snap window content to pixel by translating content by negative fractional part of the x/y
// thus if x was 5.41, snapper's x will be -0.41
// avoiding fractional window position is to avoid artifacts in text rendering when the fractional positions are present.
transform: Translate {
id: snapper
x: 0;
y: 0;
}
property var rectifier: Timer {
property bool executing: false;
interval: 100
@ -97,8 +106,8 @@ Fadable {
onTriggered: {
executing = true;
x = Math.floor(x);
y = Math.floor(y);
snapper.x = Math.floor(x) - x;
snapper.y = Math.floor(y) - y;
executing = false;
}

View file

@ -335,15 +335,17 @@ static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
// we will never drop below the 'min' value
static const int MIN_PROCESSING_THREAD_POOL_SIZE = 1;
static const QString SNAPSHOT_EXTENSION = ".jpg";
static const QString SVO_EXTENSION = ".svo";
static const QString SNAPSHOT_EXTENSION = ".jpg";
static const QString JPG_EXTENSION = ".jpg";
static const QString PNG_EXTENSION = ".png";
static const QString SVO_EXTENSION = ".svo";
static const QString SVO_JSON_EXTENSION = ".svo.json";
static const QString JSON_GZ_EXTENSION = ".json.gz";
static const QString JSON_EXTENSION = ".json";
static const QString JS_EXTENSION = ".js";
static const QString FST_EXTENSION = ".fst";
static const QString FBX_EXTENSION = ".fbx";
static const QString OBJ_EXTENSION = ".obj";
static const QString JS_EXTENSION = ".js";
static const QString FST_EXTENSION = ".fst";
static const QString FBX_EXTENSION = ".fbx";
static const QString OBJ_EXTENSION = ".obj";
static const QString AVA_JSON_EXTENSION = ".ava.json";
static const QString WEB_VIEW_TAG = "noDownload=true";
static const QString ZIP_EXTENSION = ".zip";
@ -382,7 +384,9 @@ const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensi
{ JS_EXTENSION, &Application::askToLoadScript },
{ FST_EXTENSION, &Application::askToSetAvatarUrl },
{ JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent },
{ ZIP_EXTENSION, &Application::importFromZIP }
{ ZIP_EXTENSION, &Application::importFromZIP },
{ JPG_EXTENSION, &Application::importImage },
{ PNG_EXTENSION, &Application::importImage }
};
class DeadlockWatchdogThread : public QThread {
@ -803,6 +807,8 @@ std::shared_ptr<Cube3DOverlay> _keyboardFocusHighlight{ nullptr };
OverlayID _keyboardFocusHighlightID{ UNKNOWN_OVERLAY_ID };
OffscreenGLCanvas* _qmlShareContext { nullptr };
// FIXME hack access to the internal share context for the Chromium helper
// Normally we'd want to use QWebEngine::initialize(), but we can't because
// our primary context is a QGLWidget, which can't easily be initialized to share
@ -1587,6 +1593,73 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
});
EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
// try to find the renderable
auto renderable = getEntities()->renderableForEntityId(entityID);
if (renderable) {
renderable->addMaterial(material, parentMaterialName);
}
// even if we don't find it, try to find the entity
auto entity = getEntities()->getEntity(entityID);
if (entity) {
entity->addMaterial(material, parentMaterialName);
return true;
}
return false;
});
EntityTree::setRemoveMaterialFromEntityOperator([&](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
// try to find the renderable
auto renderable = getEntities()->renderableForEntityId(entityID);
if (renderable) {
renderable->removeMaterial(material, parentMaterialName);
}
// even if we don't find it, try to find the entity
auto entity = getEntities()->getEntity(entityID);
if (entity) {
entity->removeMaterial(material, parentMaterialName);
return true;
}
return false;
});
EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto avatar = avatarManager->getAvatarBySessionID(avatarID);
if (avatar) {
avatar->addMaterial(material, parentMaterialName);
return true;
}
return false;
});
EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto avatar = avatarManager->getAvatarBySessionID(avatarID);
if (avatar) {
avatar->removeMaterial(material, parentMaterialName);
return true;
}
return false;
});
EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
auto overlay = _overlays.getOverlay(overlayID);
if (overlay) {
overlay->addMaterial(material, parentMaterialName);
return true;
}
return false;
});
EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
auto overlay = _overlays.getOverlay(overlayID);
if (overlay) {
overlay->removeMaterial(material, parentMaterialName);
return true;
}
return false;
});
// Keyboard focus handling for Web overlays.
auto overlays = &(qApp->getOverlays());
connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) {
@ -2265,18 +2338,37 @@ void Application::initializeGL() {
_isGLInitialized = true;
}
// Build a shared canvas / context for the Chromium processes
_glWidget->makeCurrent();
#if !defined(DISABLE_QML)
// Chromium rendering uses some GL functions that prevent nSight from capturing
// frames, so we only create the shared context if nsight is NOT active.
if (!nsightActive()) {
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->setObjectName("ChromiumShareContext");
_chromiumShareContext->create(_glWidget->qglContext());
_chromiumShareContext->makeCurrent();
if (!_chromiumShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make chromium shared context current");
}
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
} else {
qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
}
#endif
// Build a shared canvas / context for the QML rendering
_glWidget->makeCurrent();
_qmlShareContext = new OffscreenGLCanvas();
_qmlShareContext->setObjectName("QmlShareContext");
_qmlShareContext->create(_glWidget->qglContext());
if (!_qmlShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make QML shared context current");
}
OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
_qmlShareContext->doneCurrent();
_glWidget->makeCurrent();
gpu::Context::init<gpu::gl::GLBackend>();
qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
@ -2312,20 +2404,24 @@ void Application::initializeGL() {
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
_offscreenContext->doneCurrent();
_offscreenContext->setThreadContext();
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
// The UI can't be created until the primary OpenGL
// context is created, because it needs to share
// texture resources
// Needs to happen AFTER the render engine initialization to access its configuration
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
initializeUi();
qCDebug(interfaceapp, "Initialized Offscreen UI.");
_glWidget->makeCurrent();
// call Menu getInstance static method to set up the menu
// Needs to happen AFTER the QML UI initialization
_window->setMenuBar(Menu::getInstance());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
init();
qCDebug(interfaceapp, "init() complete.");
@ -2336,7 +2432,6 @@ void Application::initializeGL() {
_idleLoopStdev.reset();
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
// Restore the primary GL content for the main thread
if (!_offscreenContext->makeCurrent()) {
@ -2402,29 +2497,70 @@ void Application::initializeUi() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->getTablet(SYSTEM_TABLET);
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
DeadlockWatchdogThread::pause();
offscreenUi->create();
DeadlockWatchdogThread::resume();
auto surfaceContext = offscreenUi->getSurfaceContext();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
connect(offscreenUi.data(), &hifi::qml::OffscreenSurface::rootContextCreated,
this, &Application::onDesktopRootContextCreated);
connect(offscreenUi.data(), &hifi::qml::OffscreenSurface::rootItemCreated,
this, &Application::onDesktopRootItemCreated);
offscreenUi->setProxyWindow(_window->windowHandle());
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
// support the window management and scripting proxies for VR use
offscreenUi->createDesktop(PathUtils::qmlUrl("hifi/Desktop.qml"));
DeadlockWatchdogThread::withPause([&] {
offscreenUi->createDesktop(PathUtils::qmlUrl("hifi/Desktop.qml"));
});
// FIXME either expose so that dialogs can set this themselves or
// do better detection in the offscreen UI of what has focus
offscreenUi->setNavigationFocused(false);
auto engine = surfaceContext->engine();
connect(engine, &QQmlEngine::quit, [] {
qApp->quit();
});
setupPreferences();
_glWidget->installEventFilter(offscreenUi.data());
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
QPointF result = pt;
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin->isHmd()) {
getApplicationCompositor().handleRealMouseMoveEvent(false);
auto resultVec = getApplicationCompositor().getReticlePosition();
result = QPointF(resultVec.x, resultVec.y);
}
return result.toPoint();
});
offscreenUi->resume();
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
resizeGL();
});
// This will set up the input plugins UI
_activeInputPlugins.clear();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
if (KeyboardMouseDevice::NAME == inputPlugin->getName()) {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
if (TouchscreenDevice::NAME == inputPlugin->getName()) {
_touchscreenDevice = std::dynamic_pointer_cast<TouchscreenDevice>(inputPlugin);
}
}
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] {
if (isHMDMode()) {
showCursor(compositorHelper->getAllowMouseCapture() ?
Cursor::Manager::lookupIcon(_preferredCursor.get()) :
Cursor::Icon::SYSTEM);
}
});
// Pre-create a couple of Web3D overlays to speed up tablet UI
auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
}
void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
auto engine = surfaceContext->engine();
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
@ -2506,48 +2642,12 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
}
_glWidget->installEventFilter(offscreenUi.data());
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
QPointF result = pt;
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin->isHmd()) {
getApplicationCompositor().handleRealMouseMoveEvent(false);
auto resultVec = getApplicationCompositor().getReticlePosition();
result = QPointF(resultVec.x, resultVec.y);
}
return result.toPoint();
});
offscreenUi->resume();
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
resizeGL();
});
// This will set up the input plugins UI
_activeInputPlugins.clear();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
if (KeyboardMouseDevice::NAME == inputPlugin->getName()) {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
if (TouchscreenDevice::NAME == inputPlugin->getName()) {
_touchscreenDevice = std::dynamic_pointer_cast<TouchscreenDevice>(inputPlugin);
}
}
_window->setMenuBar(new Menu());
}
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] {
if (isHMDMode()) {
showCursor(compositorHelper->getAllowMouseCapture() ?
Cursor::Manager::lookupIcon(_preferredCursor.get()) :
Cursor::Icon::SYSTEM);
}
});
// Pre-create a couple of Web3D overlays to speed up tablet UI
auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
Stats::show();
AvatarInputs::show();
}
void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
@ -2786,6 +2886,8 @@ void Application::resizeGL() {
QMutexLocker viewLocker(&_viewMutex);
_myCamera.loadViewFrustum(_viewFrustum);
}
DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
}
void Application::handleSandboxStatus(QNetworkReply* reply) {
@ -2901,6 +3003,14 @@ bool Application::importFromZIP(const QString& filePath) {
return true;
}
bool Application::importImage(const QString& urlString) {
qCDebug(interfaceapp) << "An image file has been dropped in";
QString filepath(urlString);
filepath.remove("file:///");
addAssetToWorld(filepath, "", false, false);
return true;
}
// thread-safe
void Application::onPresent(quint32 frameCount) {
bool expected = false;
@ -3994,7 +4104,7 @@ void Application::idle() {
// Bit of a hack since there's no device pixel ratio change event I can find.
if (offscreenUi->size() != fromGlm(uiSize)) {
qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize;
offscreenUi->resize(fromGlm(uiSize), true);
offscreenUi->resize(fromGlm(uiSize));
_offscreenContext->makeCurrent();
}
}
@ -4719,7 +4829,6 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
if (_keyboardFocusHighlightID == UNKNOWN_OVERLAY_ID || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
_keyboardFocusHighlight = std::make_shared<Cube3DOverlay>();
_keyboardFocusHighlight->setAlpha(1.0f);
_keyboardFocusHighlight->setBorderSize(1.0f);
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
_keyboardFocusHighlight->setIsSolid(false);
_keyboardFocusHighlight->setPulseMin(0.5);
@ -5859,7 +5968,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(toolbarScriptingInterface);
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine.data(), CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue);
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter, "Window");
// register `location` on the global object.
@ -6493,7 +6601,8 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
} else {
// to prevent files that aren't models from being loaded into world automatically
if (filePath.endsWith(".obj") || filePath.endsWith(".fbx")) {
if (filePath.endsWith(OBJ_EXTENSION) || filePath.endsWith(FBX_EXTENSION) ||
filePath.endsWith(JPG_EXTENSION) || filePath.endsWith(PNG_EXTENSION)) {
addAssetToWorldAddEntity(filePath, mapping);
} else {
qCDebug(interfaceapp) << "Zipped contents are not supported entity files";
@ -6510,8 +6619,17 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
EntityItemProperties properties;
properties.setType(EntityTypes::Model);
properties.setName(mapping.right(mapping.length() - 1));
properties.setModelURL("atp:" + mapping);
properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
if (filePath.endsWith(PNG_EXTENSION) || filePath.endsWith(JPG_EXTENSION)) {
QJsonObject textures {
{"tex.picture", QString("atp:" + mapping) }
};
properties.setModelURL("https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx");
properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact));
properties.setShapeType(SHAPE_TYPE_BOX);
} else {
properties.setModelURL("atp:" + mapping);
properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
}
properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar.
properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions.
glm::vec3 positionOffset = getMyAvatar()->getWorldOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f));
@ -7326,9 +7444,7 @@ void Application::updateDisplayMode() {
action->setChecked(true);
}
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);

View file

@ -391,6 +391,8 @@ public slots:
void setPreferredCursor(const QString& cursor);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);
void onDesktopRootContextCreated(QQmlContext* qmlContext);
void showDesktop();
void clearDomainOctreeDetails();
void clearDomainAvatars();
@ -471,6 +473,7 @@ private:
bool importJSONFromURL(const QString& urlString);
bool importSVOFromURL(const QString& urlString);
bool importFromZIP(const QString& filePath);
bool importImage(const QString& urlString);
bool nearbyEntitiesAreReadyForPhysics();
int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode);

View file

@ -210,6 +210,7 @@ Menu::Menu() {
auto avatarEntitiesBookmarks = DependencyManager::get<AvatarEntitiesBookmarks>();
avatarEntitiesBookmarks->setupMenus(this, avatarMenu);
// Display menu ----------------------------------
// FIXME - this is not yet matching Alan's spec because it doesn't have
// menus for "2D"/"3D" - we need to add support for detecting the appropriate

View file

@ -1111,7 +1111,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
}
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
}
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
@ -1460,10 +1460,23 @@ void MyAvatar::clearJointsData() {
}
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelChangeCount++;
int skeletonModelChangeCount = _skeletonModelChangeCount;
Avatar::setSkeletonModelURL(skeletonModelURL);
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
_headBoneSet.clear();
_cauterizationNeedsUpdate = true;
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
initHeadBones();
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph();
}
QObject::disconnect(*skeletonConnection);
});
saveAvatarUrl();
emit skeletonChanged();
@ -1808,7 +1821,7 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName,
void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) {
if (model->isActive() && model->isRenderable()) {
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE);
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true);
}
}
@ -1872,9 +1885,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) {
_currentAnimGraphUrl.set(url);
_skeletonModel->getRig().initAnimGraph(url);
_bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
}
void MyAvatar::initAnimGraph() {
@ -1889,28 +1900,24 @@ void MyAvatar::initAnimGraph() {
_skeletonModel->getRig().initAnimGraph(graphUrl);
_currentAnimGraphUrl.set(graphUrl);
_bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
}
void MyAvatar::destroyAnimGraph() {
_skeletonModel->getRig().destroyAnimGraph();
}
void MyAvatar::animGraphLoaded() {
_bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes
_isAnimatingScale = true;
_cauterizationNeedsUpdate = true;
disconnect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
}
void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
Avatar::postUpdate(deltaTime, scene);
if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode()) {
initHeadBones();
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph();
_isAnimatingScale = true;
_cauterizationNeedsUpdate = true;
}
if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) {
auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton();
@ -2008,7 +2015,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(),
render::ItemKey::TAG_BITS_NONE);
render::ItemKey::TAG_BITS_NONE, true);
}
}
}

View file

@ -569,6 +569,7 @@ public slots:
void increaseSize();
void decreaseSize();
void resetSize();
void animGraphLoaded();
void setGravity(float gravity);
float getGravity();
@ -654,6 +655,7 @@ private:
bool isMyAvatar() const override { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
virtual glm::vec3 getSkeletonPosition() const override;
int _skeletonModelChangeCount { 0 };
void saveAvatarScale();

View file

@ -1,8 +1,10 @@
#include "LimitlessConnection.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h>
#include "LimitlessConnection.h"
#include "LimitlessVoiceRecognitionScriptingInterface.h"
LimitlessConnection::LimitlessConnection() :

View file

@ -94,22 +94,6 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
return result;
}
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected) {
static const char* slot = SLOT(menuItemTriggered());
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
Q_ARG(const QString&, groupName),
Q_ARG(const QStringList&, actionList),
Q_ARG(const QString&, selected),
Q_ARG(QObject*, this),
Q_ARG(const char*, slot));
}
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {
QMetaObject::invokeMethod(Menu::getInstance(), "removeActionGroup",
Q_ARG(const QString&, groupName));
}
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->isOptionChecked(menuOption);
@ -147,19 +131,3 @@ void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isCh
void MenuScriptingInterface::triggerOption(const QString& menuOption) {
QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption));
}
void MenuScriptingInterface::closeInfoView(const QString& path) {
QMetaObject::invokeMethod(Menu::getInstance(), "closeInfoView", Q_ARG(const QString&, path));
}
bool MenuScriptingInterface::isInfoViewVisible(const QString& path) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->isInfoViewVisible(path);
}
bool result;
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isInfoViewVisible",
Q_RETURN_ARG(bool, result), Q_ARG(const QString&, path));
return result;
}

View file

@ -163,13 +163,6 @@ public slots:
*/
bool menuItemExists(const QString& menuName, const QString& menuitem);
/**
* TODO: Not working; don't document until fixed.
*/
void addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected = QString());
void removeActionGroup(const QString& groupName);
/**jsdoc
* Check whether a checkable menu item is checked.
* @function Menu.isOptionChecked
@ -222,12 +215,6 @@ public slots:
*/
void setMenuEnabled(const QString& menuName, bool isEnabled);
/**
* TODO: Not used or useful; will not document until used.
*/
void closeInfoView(const QString& path);
bool isInfoViewVisible(const QString& path);
signals:
/**jsdoc
* Triggered when a menu item is clicked (or triggered by {@link Menu.triggerOption}).

View file

@ -32,20 +32,6 @@ static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
static const QString LAST_BROWSE_ASSETS_LOCATION_SETTING = "LastBrowseAssetsLocation";
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result) {
if (!result.value.isValid()) {
return QScriptValue::UndefinedValue;
}
Q_ASSERT(result.value.userType() == qMetaTypeId<QVariantMap>());
return engine->toScriptValue(result.value.toMap());
}
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result) {
result.value = object.toVariant();
}
WindowScriptingInterface::WindowScriptingInterface() {
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
@ -120,16 +106,19 @@ QScriptValue WindowScriptingInterface::confirm(const QString& message) {
/// \param const QString& defaultText default text in the text box
/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
bool ok = false;
QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText, &ok);
return ok ? QScriptValue(result) : QScriptValue::NullValue;
QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText);
if (QScriptValue(result).equals("")) {
return QScriptValue::NullValue;
}
return QScriptValue(result);
}
/// Display a prompt with a text box
/// \param const QString& message message to display
/// \param const QString& defaultText default text in the text box
void WindowScriptingInterface::promptAsync(const QString& message, const QString& defaultText) {
ModalDialogListener* dlg = OffscreenUi::getTextAsync(nullptr, "", message, QLineEdit::Normal, defaultText);
bool ok = false;
ModalDialogListener* dlg = OffscreenUi::getTextAsync(nullptr, "", message, QLineEdit::Normal, defaultText, &ok);
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant result) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
emit promptTextChanged(result.toString());
@ -140,14 +129,6 @@ void WindowScriptingInterface::disconnectedFromDomain() {
emit domainChanged("");
}
CustomPromptResult WindowScriptingInterface::customPrompt(const QVariant& config) {
CustomPromptResult result;
bool ok = false;
auto configMap = config.toMap();
result.value = OffscreenUi::getCustomInfo(OffscreenUi::ICON_NONE, "", configMap, &ok);
return ok ? result : CustomPromptResult();
}
QString fixupPathForMac(const QString& directory) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.

View file

@ -22,17 +22,6 @@
#include <DependencyManager.h>
class CustomPromptResult {
public:
QVariant value;
};
Q_DECLARE_METATYPE(CustomPromptResult);
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result);
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result);
/**jsdoc
* The Window API provides various facilities not covered elsewhere: window dimensions, window focus, normal or entity camera
* view, clipboard, announcements, user connections, common dialog boxes, snapshots, file import, domain changes, domain
@ -142,15 +131,6 @@ public slots:
*/
void promptAsync(const QString& message = "", const QString& defaultText = "");
/**jsdoc
* Prompt the user for input in a custom, modal dialog.
* @deprecated This function is deprecated and will soon be removed.
* @function Window.customPrompt
* @param {object} config - Configures the modal dialog.
* @returns {object} The user's response.
*/
CustomPromptResult customPrompt(const QVariant& config);
/**jsdoc
* Prompt the user to choose a directory. Displays a modal dialog that navigates the directory tree.
* @function Window.browseDir

View file

@ -42,8 +42,6 @@ private:
int _domainStatusBorder;
int _magnifierBorder;
ivec2 _previousBorderSize{ -1 };
gpu::TexturePointer _uiTexture;
gpu::TexturePointer _overlayDepthTexture;
gpu::TexturePointer _overlayColorTexture;

View file

@ -25,7 +25,6 @@ Setting::Handle<bool> showAudioToolsSetting { QStringList { "AvatarInputs", "sho
AvatarInputs* AvatarInputs::getInstance() {
if (!INSTANCE) {
AvatarInputs::registerType();
AvatarInputs::show();
Q_ASSERT(INSTANCE);
}
return INSTANCE;

View file

@ -48,7 +48,6 @@ QString getTextureMemoryPressureModeString();
Stats* Stats::getInstance() {
if (!INSTANCE) {
Stats::registerType();
Stats::show();
Q_ASSERT(INSTANCE);
}
return INSTANCE;

View file

@ -425,10 +425,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
* <em>Write-only.</em>
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
* <em>Write-only.</em>
* @property {Color} innerStartcolor - The color at the inner start point of the overlay. <em>Write-only.</em>
* @property {Color} innerEndColor - The color at the inner end point of the overlay. <em>Write-only.</em>
* @property {Color} outerStartColor - The color at the outer start point of the overlay. <em>Write-only.</em>
* @property {Color} outerEndColor - The color at the outer end point of the overlay. <em>Write-only.</em>
* @property {Color} innerStartcolor - The color at the inner start point of the overlay.
* @property {Color} innerEndColor - The color at the inner end point of the overlay.
* @property {Color} outerStartColor - The color at the outer start point of the overlay.
* @property {Color} outerEndColor - The color at the outer end point of the overlay.
* @property {number} alpha=0.5 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. Setting this value also sets
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write-only</em>.
@ -440,10 +440,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
* <em>Write-only.</em>
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write-only.</em>
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. <em>Write-only.</em>
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. <em>Write-only.</em>
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. <em>Write-only.</em>
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. <em>Write-only.</em>
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay.
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay.
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay.
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay.
* @property {boolean} hasTickMarks=false - If <code>true</code>, tick marks are drawn.
* @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees.

View file

@ -125,13 +125,6 @@ Cube3DOverlay* Cube3DOverlay::createClone() const {
void Cube3DOverlay::setProperties(const QVariantMap& properties) {
Volume3DOverlay::setProperties(properties);
auto borderSize = properties["borderSize"];
if (borderSize.isValid()) {
float value = borderSize.toFloat();
setBorderSize(value);
}
}
/**jsdoc
@ -177,14 +170,8 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {number} borderSize - Not used.
*/
QVariant Cube3DOverlay::getProperty(const QString& property) {
if (property == "borderSize") {
return _borderSize;
}
return Volume3DOverlay::getProperty(property);
}

View file

@ -29,10 +29,6 @@ public:
virtual Cube3DOverlay* createClone() const override;
float getBorderSize() const { return _borderSize; }
void setBorderSize(float value) { _borderSize = value; }
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
@ -40,7 +36,6 @@ protected:
Transform evalRenderTransform() override;
private:
float _borderSize;
// edges on a cube
std::array<int, 12> _geometryIds;
};

View file

@ -83,11 +83,12 @@ void ModelOverlay::update(float deltatime) {
auto modelOverlay = static_cast<ModelOverlay*>(&data);
modelOverlay->setSubRenderItemIDs(newRenderItemIDs);
});
processMaterials();
}
if (_visibleDirty) {
_visibleDirty = false;
// don't show overlays in mirrors
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0);
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false);
}
if (_drawInFrontDirty) {
_drawInFrontDirty = false;
@ -108,6 +109,7 @@ void ModelOverlay::update(float deltatime) {
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
Volume3DOverlay::addToScene(overlay, scene, transaction);
_model->addToScene(scene, transaction);
processMaterials();
return true;
}
@ -632,3 +634,29 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const {
}
return 0;
}
void ModelOverlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
Overlay::addMaterial(material, parentMaterialName);
if (_model && _model->fetchRenderItemIDs().size() > 0) {
_model->addMaterial(material, parentMaterialName);
}
}
void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
Overlay::removeMaterial(material, parentMaterialName);
if (_model && _model->fetchRenderItemIDs().size() > 0) {
_model->removeMaterial(material, parentMaterialName);
}
}
void ModelOverlay::processMaterials() {
assert(_model);
std::lock_guard<std::mutex> lock(_materialsLock);
for (auto& shapeMaterialPair : _materials) {
auto material = shapeMaterialPair.second;
while (!material.empty()) {
_model->addMaterial(material.top(), shapeMaterialPair.first);
material.pop();
}
}
}

View file

@ -59,6 +59,9 @@ public:
void setDrawInFront(bool drawInFront) override;
void setDrawHUDLayer(bool drawHUDLayer) override;
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
protected:
Transform evalRenderTransform() override;
@ -110,6 +113,8 @@ private:
bool _drawInFrontDirty { false };
bool _drawInHUDDirty { false };
void processMaterials();
};
#endif // hifi_ModelOverlay_h

View file

@ -235,3 +235,13 @@ QVector<OverlayID> qVectorOverlayIDFromScriptValue(const QScriptValue& array) {
}
return newVector;
}
void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material);
}
void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
}

View file

@ -91,6 +91,9 @@ public:
unsigned int getStackOrder() const { return _stackOrder; }
void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; }
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
protected:
float updatePulse();
@ -117,6 +120,9 @@ protected:
static const xColor DEFAULT_OVERLAY_COLOR;
static const float DEFAULT_ALPHA;
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;
private:
OverlayID _overlayID; // only used for non-3d overlays
};

View file

@ -388,21 +388,6 @@ QString Overlays::getOverlayType(OverlayID overlayId) {
return "";
}
QObject* Overlays::getOverlayObject(OverlayID id) {
if (QThread::currentThread() != thread()) {
QObject* result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id));
return result;
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
return qobject_cast<QObject*>(&(*thisOverlay));
}
return nullptr;
}
OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
if (!_enabled) {
return UNKNOWN_OVERLAY_ID;

View file

@ -98,6 +98,11 @@ public:
OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
OverlayID addOverlay(const Overlay::Pointer& overlay);
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
bool mousePressEvent(QMouseEvent* event);
bool mouseDoublePressEvent(QMouseEvent* event);
bool mouseReleaseEvent(QMouseEvent* event);
@ -227,15 +232,6 @@ public slots:
*/
QString getOverlayType(OverlayID overlayId);
/**jsdoc
* Get the overlay script object.
* @function Overlays.getOverlayObject
* @deprecated This function is deprecated and will soon be removed.
* @param {Uuid} overlayID - The ID of the overlay to get the script object of.
* @returns {object} The script object for the overlay if found.
*/
QObject* getOverlayObject(OverlayID id);
/**jsdoc
* Get the ID of the 2D overlay at a particular point on the screen or HUD.
* @function Overlays.getOverlayAtPoint
@ -356,28 +352,6 @@ public slots:
bool visibleOnly = false,
bool collidableOnly = false);
// TODO: Apart from the name, this function signature on JavaScript is identical to that of findRayIntersection() and should
// probably be removed from the JavaScript API so as not to cause confusion.
/**jsdoc
* Find the closest 3D overlay intersected by a {@link PickRay}.
* @function Overlays.findRayIntersectionVector
* @deprecated Use {@link Overlays.findRayIntersection} instead; it has identical parameters and results.
* @param {PickRay} pickRay - The PickRay to use for finding overlays.
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
* @param {Array.<Uuid>} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited
* to overlays in the list.
* @param {Array.<Uuid>} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't
* exclude overlays in the list.
* @param {boolean} [visibleOnly=false] - <em>Unused</em>; exists to match Entity API.
* @param {boolean} [collidableOnly=false] - <em>Unused</em>; exists to match Entity API.
* @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by <code>pickRay</code>, taking
* into account <code>overlayIDsToInclude</code> and <code>overlayIDsToExclude</code> if they're not empty.
*/
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
/**jsdoc
* Return a list of 3D overlays with bounding boxes that touch a search sphere.
* @function Overlays.findOverlays

View file

@ -99,13 +99,6 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
}
}
}
auto borderSize = properties["borderSize"];
if (borderSize.isValid()) {
float value = borderSize.toFloat();
setBorderSize(value);
}
}
/**jsdoc
@ -153,13 +146,8 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {Shape} shape=Hexagon - The geometrical shape of the overlay.
* @property {number} borderSize - Not used.
*/
QVariant Shape3DOverlay::getProperty(const QString& property) {
if (property == "borderSize") {
return _borderSize;
}
if (property == "shape") {
return shapeStrings[_shape];
}

View file

@ -30,10 +30,6 @@ public:
virtual Shape3DOverlay* createClone() const override;
float getBorderSize() const { return _borderSize; }
void setBorderSize(float value) { _borderSize = value; }
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
@ -41,7 +37,6 @@ protected:
Transform evalRenderTransform() override;
private:
float _borderSize;
GeometryCache::Shape _shape { GeometryCache::Hexagon };
};

View file

@ -134,7 +134,11 @@ void Web3DOverlay::destroyWebSurface() {
QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
DependencyManager::get<OffscreenQmlSurfaceCache>()->release(QML, _webSurface);
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
}
_webSurface.reset();
}

View file

@ -38,6 +38,8 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q
static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f;
// called after children have been loaded
// returns node on success, nullptr on failure.
static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
@ -653,6 +655,7 @@ AnimNodeLoader::AnimNodeLoader(const QUrl& url) :
{
_resource = QSharedPointer<Resource>::create(url);
_resource->setSelf(_resource);
_resource->setLoadPriority(this, ANIM_GRAPH_LOAD_PRIORITY);
connect(_resource.data(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone);
connect(_resource.data(), &Resource::failed, this, &AnimNodeLoader::onRequestError);
_resource->ensureLoading();

View file

@ -434,9 +434,13 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
bool success { false };
if (QThread::currentThread() == thread()) {
glm::vec3 originalPosition = position;
bool onOwnerThread = (QThread::currentThread() == thread());
glm::vec3 poseSetTrans;
if (onOwnerThread) {
if (isIndexValid(jointIndex)) {
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
poseSetTrans = _internalPoseSet._absolutePoses[jointIndex].trans();
position = (rotation * poseSetTrans) + translation;
success = true;
} else {
success = false;
@ -444,7 +448,8 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
} else {
QReadLocker readLock(&_externalPoseSetLock);
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation;
poseSetTrans = _externalPoseSet._absolutePoses[jointIndex].trans();
position = (rotation * poseSetTrans) + translation;
success = true;
} else {
success = false;
@ -452,7 +457,14 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
}
if (isNaN(position)) {
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produces NaN";
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produced NaN."
<< " is owner thread = " << onOwnerThread
<< " position = " << originalPosition
<< " translation = " << translation
<< " rotation = " << rotation
<< " poseSetTrans = " << poseSetTrans
<< " success = " << success
<< " jointIndex = " << jointIndex;
success = false;
position = glm::vec3(0.0f);
}
@ -1585,14 +1597,13 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
}
void Rig::initAnimGraph(const QUrl& url) {
if (_animGraphURL != url || (!_animNode && !_animLoading)) {
if (_animGraphURL != url || !_animNode) {
_animGraphURL = url;
_animNode.reset();
// load the anim graph
_animLoader.reset(new AnimNodeLoader(url));
_animLoading = true;
std::weak_ptr<AnimSkeleton> weakSkeletonPtr = _animSkeleton;
connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) {
_animNode = nodeIn;
@ -1617,7 +1628,6 @@ void Rig::initAnimGraph(const QUrl& url) {
auto roleState = roleAnimState.second;
overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame);
}
_animLoading = false;
emit onLoadComplete();
});

View file

@ -283,7 +283,6 @@ protected:
std::shared_ptr<AnimNode> _animNode;
std::shared_ptr<AnimSkeleton> _animSkeleton;
std::unique_ptr<AnimNodeLoader> _animLoader;
bool _animLoading { false };
AnimVariantMap _animVars;
enum class RigRole {
Idle = 0,

View file

@ -13,5 +13,6 @@ include_hifi_library_headers(entities-renderer)
include_hifi_library_headers(audio)
include_hifi_library_headers(entities)
include_hifi_library_headers(octree)
include_hifi_library_headers(task)
target_bullet()

View file

@ -50,7 +50,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1);
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withMetaCullGroup();
}
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
return static_pointer_cast<Avatar>(avatar)->getBounds();
@ -219,7 +219,7 @@ void Avatar::updateAvatarEntities() {
return;
}
if (getID() == QUuid()) {
if (getID() == QUuid() || getID() == AVATAR_SELF_ID) {
return; // wait until MyAvatar gets an ID before doing this.
}
@ -570,6 +570,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc
}
transaction.resetItem(_renderItemID, avatarPayloadPointer);
_skeletonModel->addToScene(scene, transaction);
processMaterials();
for (auto& attachmentModel : _attachmentModels) {
attachmentModel->addToScene(scene, transaction);
}
@ -761,6 +762,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) {
_skeletonModel->removeFromScene(scene, transaction);
_skeletonModel->addToScene(scene, transaction);
processMaterials();
canTryFade = true;
_isAnimatingScale = true;
}
@ -1760,3 +1762,31 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
return DEFAULT_AVATAR_EYE_HEIGHT;
}
}
void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material);
if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) {
_skeletonModel->addMaterial(material, parentMaterialName);
}
}
void Avatar::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) {
_skeletonModel->removeMaterial(material, parentMaterialName);
}
}
void Avatar::processMaterials() {
assert(_skeletonModel);
std::lock_guard<std::mutex> lock(_materialsLock);
for (auto& shapeMaterialPair : _materials) {
auto material = shapeMaterialPair.second;
while (!material.empty()) {
_skeletonModel->addMaterial(material.top(), shapeMaterialPair.first);
material.pop();
}
}
}

View file

@ -272,6 +272,9 @@ public:
virtual void setAvatarEntityDataChanged(bool value) override;
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
public slots:
// FIXME - these should be migrated to use Pose data instead
@ -397,6 +400,11 @@ protected:
float _displayNameAlpha { 1.0f };
ThreadSafeValueCache<float> _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT };
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;
void processMaterials();
};
#endif // hifi_Avatar_h

View file

@ -1,3 +1,4 @@
set(TARGET_NAME avatars)
setup_hifi_library(Network Script)
link_hifi_libraries(shared networking)
include_hifi_library_headers(gpu)
link_hifi_libraries(shared networking graphics)

View file

@ -2558,4 +2558,4 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap&
value[EntityID] = binaryEntityProperties;
}
}
}

View file

@ -54,6 +54,8 @@
#include "HeadData.h"
#include "PathUtils.h"
#include <graphics/Material.h>
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
@ -694,6 +696,9 @@ public:
bool getIsReplicated() const { return _isReplicated; }
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {}
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {}
signals:
void displayNameChanged();
void sessionDisplayNameChanged();

View file

@ -30,6 +30,20 @@ QVector<QUuid> AvatarHashMap::getAvatarIdentifiers() {
return _avatarHash.keys().toVector();
}
QVector<QUuid> AvatarHashMap::getAvatarsInRange(const glm::vec3& position, float rangeMeters) const {
auto hashCopy = getHashCopy();
QVector<QUuid> avatarsInRange;
auto rangeMetersSquared = rangeMeters * rangeMeters;
for (const AvatarSharedPointer& sharedAvatar : hashCopy) {
glm::vec3 avatarPosition = sharedAvatar->getWorldPosition();
auto distanceSquared = glm::distance2(avatarPosition, position);
if (distanceSquared < rangeMetersSquared) {
avatarsInRange.push_back(sharedAvatar->getSessionUUID());
}
}
return avatarsInRange;
}
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
auto hashCopy = getHashCopy();
foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) {

View file

@ -35,10 +35,12 @@ class AvatarHashMap : public QObject, public Dependency {
public:
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; }
int size() { return _avatarHash.size(); }
// Currently, your own avatar will be included as the null avatar id.
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
Q_INVOKABLE QVector<QUuid> getAvatarsInRange(const glm::vec3& position, float rangeMeters) const;
// Null/Default-constructed QUuids will return MyAvatar
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }

View file

@ -27,6 +27,7 @@
#include <gl/GLWidget.h>
#include <gl/GLEscrow.h>
#include <gl/Context.h>
#include <gl/OffscreenGLCanvas.h>
#include <gpu/Texture.h>
#include <gpu/StandardShaderLib.h>
@ -130,14 +131,14 @@ public:
CHECK_GL_ERROR();
_context->doneCurrent();
while (!_shutdown) {
if (_pendingMainThreadOperation) {
if (_pendingOtherThreadOperation) {
PROFILE_RANGE(render, "MainThreadOp")
{
Lock lock(_mutex);
_context->doneCurrent();
// Move the context to the main thread
_context->moveToThread(qApp->thread());
_pendingMainThreadOperation = false;
_context->moveToThread(_targetOperationThread);
_pendingOtherThreadOperation = false;
// Release the main thread to do it's action
_condition.notify_one();
}
@ -146,7 +147,7 @@ public:
{
// Main thread does it's thing while we wait on the lock to release
Lock lock(_mutex);
_condition.wait(lock, [&] { return _finishedMainThreadOperation; });
_condition.wait(lock, [&] { return _finishedOtherThreadOperation; });
}
}
@ -214,23 +215,25 @@ public:
_condition.notify_one();
}
void withMainThreadContext(std::function<void()> f) {
void withOtherThreadContext(std::function<void()> f) {
// Signal to the thread that there is work to be done on the main thread
Lock lock(_mutex);
_pendingMainThreadOperation = true;
_finishedMainThreadOperation = false;
_condition.wait(lock, [&] { return !_pendingMainThreadOperation; });
_targetOperationThread = QThread::currentThread();
_pendingOtherThreadOperation = true;
_finishedOtherThreadOperation = false;
_condition.wait(lock, [&] { return !_pendingOtherThreadOperation; });
_context->makeCurrent();
f();
_context->doneCurrent();
_targetOperationThread = nullptr;
// Move the context back to the presentation thread
_context->moveToThread(this);
// restore control of the context to the presentation thread and signal
// the end of the operation
_finishedMainThreadOperation = true;
_finishedOtherThreadOperation = true;
lock.unlock();
_condition.notify_one();
}
@ -244,9 +247,11 @@ private:
Mutex _mutex;
// Used to allow the main thread to perform context operations
Condition _condition;
bool _pendingMainThreadOperation { false };
bool _finishedMainThreadOperation { false };
QThread* _mainThread { nullptr };
QThread* _targetOperationThread { nullptr };
bool _pendingOtherThreadOperation { false };
bool _finishedOtherThreadOperation { false };
std::queue<OpenGLDisplayPlugin*> _newPluginQueue;
gl::Context* _context { nullptr };
};
@ -744,10 +749,12 @@ void OpenGLDisplayPlugin::swapBuffers() {
context->swapBuffers();
}
void OpenGLDisplayPlugin::withMainThreadContext(std::function<void()> f) const {
void OpenGLDisplayPlugin::withOtherThreadContext(std::function<void()> f) const {
static auto presentThread = DependencyManager::get<PresentThread>();
presentThread->withMainThreadContext(f);
_container->makeRenderingContextCurrent();
presentThread->withOtherThreadContext(f);
if (!OffscreenGLCanvas::restoreThreadContext()) {
qWarning("Unable to restore original OpenGL context");
}
}
bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
@ -784,7 +791,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
}
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
withMainThreadContext([&] {
withOtherThreadContext([&] {
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
});
return screenshot.mirrored(false, true);
@ -797,7 +804,7 @@ QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const {
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
QImage screenshot(region.z, region.w, QImage::Format_ARGB32);
withMainThreadContext([&] {
withOtherThreadContext([&] {
glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
});
return screenshot.mirrored(false, true);
@ -886,7 +893,7 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) {
#if !defined(USE_GLES)
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
withMainThreadContext([&] {
withOtherThreadContext([&] {
GLuint sourceTexture = glBackend->getTextureID(networkTexture->getGPUTexture());
GLuint targetTexture = target->texture();
GLuint fbo[2] {0, 0};

View file

@ -119,7 +119,7 @@ protected:
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor);
virtual void updateFrameData();
void withMainThreadContext(std::function<void()> f) const;
void withOtherThreadContext(std::function<void()> f) const;
void present();
virtual void swapBuffers();

View file

@ -79,7 +79,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
QHash<QByteArray, QByteArray> redirectHeader;
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader);
}
// if the last thing is a trailing slash then we want to look for index file

View file

@ -1,7 +1,7 @@
set(TARGET_NAME entities-renderer)
AUTOSCRIBE_SHADER_LIB(gpu graphics procedural render render-utils)
setup_hifi_library(Network Script)
link_hifi_libraries(shared gpu procedural graphics model-networking script-engine render render-utils image ui pointers)
link_hifi_libraries(shared gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers)
include_hifi_library_headers(networking)
include_hifi_library_headers(gl)
include_hifi_library_headers(ktx)
@ -13,6 +13,7 @@ include_hifi_library_headers(fbx)
include_hifi_library_headers(entities)
include_hifi_library_headers(avatars)
include_hifi_library_headers(controllers)
include_hifi_library_headers(task)
target_bullet()
target_polyvox()

View file

@ -25,6 +25,7 @@
#include "RenderableTextEntityItem.h"
#include "RenderableWebEntityItem.h"
#include "RenderableZoneEntityItem.h"
#include "RenderableMaterialEntityItem.h"
using namespace render;
@ -145,6 +146,7 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity
_needsRenderUpdate = true;
emit requestRenderUpdate();
});
_materials = entity->getMaterials();
}
EntityRenderer::~EntityRenderer() { }
@ -252,6 +254,10 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer,
result = make_renderer<ZoneEntityRenderer>(entity);
break;
case Type::Material:
result = make_renderer<MaterialEntityRenderer>(entity);
break;
default:
break;
}
@ -395,3 +401,13 @@ void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) {
entity->deregisterChangeHandler(_changeHandlerId);
QObject::disconnect(this, &EntityRenderer::requestRenderUpdate, this, nullptr);
}
void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material);
}
void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
}

View file

@ -54,12 +54,14 @@ public:
const uint64_t& getUpdateTime() const { return _updateTime; }
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
protected:
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
virtual void onAddToScene(const EntityItemPointer& entity);
virtual void onRemoveFromScene(const EntityItemPointer& entity);
protected:
EntityRenderer(const EntityItemPointer& entity);
~EntityRenderer();
@ -130,6 +132,8 @@ protected:
// Only touched on the rendering thread
bool _renderUpdateQueued{ false };
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;
private:
// The base class relies on comparing the model transform to the entity transform in order

View file

@ -0,0 +1,269 @@
//
// Created by Sam Gondelman on 1/18/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RenderableMaterialEntityItem.h"
#include "RenderPipelines.h"
using namespace render;
using namespace render::entities;
bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (entity->getMaterial() != _drawMaterial) {
return true;
}
if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) {
return true;
}
if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) {
return true;
}
return false;
}
void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
withWriteLock([&] {
_drawMaterial = entity->getMaterial();
_parentID = entity->getParentID();
_clientOnly = entity->getClientOnly();
_owningAvatarID = entity->getOwningAvatarID();
_materialMappingPos = entity->getMaterialMappingPos();
_materialMappingScale = entity->getMaterialMappingScale();
_materialMappingRot = entity->getMaterialMappingRot();
_renderTransform = getModelTransform();
const float MATERIAL_ENTITY_SCALE = 0.5f;
_renderTransform.postScale(MATERIAL_ENTITY_SCALE);
_renderTransform.postScale(ENTITY_ITEM_DEFAULT_DIMENSIONS);
});
}
ItemKey MaterialEntityRenderer::getKey() {
ItemKey::Builder builder;
builder.withTypeShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1);
if (!_visible) {
builder.withInvisible();
}
if (_drawMaterial) {
auto matKey = _drawMaterial->getKey();
if (matKey.isTranslucent()) {
builder.withTransparent();
}
}
return builder.build();
}
ShapeKey MaterialEntityRenderer::getShapeKey() {
graphics::MaterialKey drawMaterialKey;
if (_drawMaterial) {
drawMaterialKey = _drawMaterial->getKey();
}
bool isTranslucent = drawMaterialKey.isTranslucent();
bool hasTangents = drawMaterialKey.isNormalMap();
bool hasSpecular = drawMaterialKey.isMetallicMap();
bool hasLightmap = drawMaterialKey.isLightmapMap();
bool isUnlit = drawMaterialKey.isUnlit();
ShapeKey::Builder builder;
builder.withMaterial();
if (isTranslucent) {
builder.withTranslucent();
}
if (hasTangents) {
builder.withTangents();
}
if (hasSpecular) {
builder.withSpecular();
}
if (hasLightmap) {
builder.withLightmap();
}
if (isUnlit) {
builder.withUnlit();
}
return builder.build();
}
glm::vec3 MaterialEntityRenderer::getVertexPos(float phi, float theta) {
return glm::vec3(glm::sin(theta) * glm::cos(phi), glm::cos(theta), glm::sin(theta) * glm::sin(phi));
}
glm::vec3 MaterialEntityRenderer::getTangent(float phi, float theta) {
return glm::vec3(-glm::cos(theta) * glm::cos(phi), glm::sin(theta), -glm::cos(theta) * glm::sin(phi));
}
void MaterialEntityRenderer::addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv) {
buffer.push_back(pos.x); buffer.push_back(pos.y); buffer.push_back(pos.z);
buffer.push_back(tan.x); buffer.push_back(tan.y); buffer.push_back(tan.z);
buffer.push_back(uv.x); buffer.push_back(uv.y);
}
void MaterialEntityRenderer::addTriangleFan(std::vector<float>& buffer, int stack, int step) {
float v1 = ((float)stack) / STACKS;
float theta1 = v1 * (float)M_PI;
glm::vec3 tip = getVertexPos(0, theta1);
float v2 = ((float)(stack + step)) / STACKS;
float theta2 = v2 * (float)M_PI;
for (int i = 0; i < SLICES; i++) {
float u1 = ((float)i) / SLICES;
float u2 = ((float)(i + step)) / SLICES;
float phi1 = u1 * M_PI_TIMES_2;
float phi2 = u2 * M_PI_TIMES_2;
/* (flipped for negative step)
p1
/ \
/ \
/ \
p3 ------ p2
*/
glm::vec3 pos2 = getVertexPos(phi2, theta2);
glm::vec3 pos3 = getVertexPos(phi1, theta2);
glm::vec3 tan1 = getTangent(0, theta1);
glm::vec3 tan2 = getTangent(phi2, theta2);
glm::vec3 tan3 = getTangent(phi1, theta2);
glm::vec2 uv1 = glm::vec2((u1 + u2) / 2.0f, v1);
glm::vec2 uv2 = glm::vec2(u2, v2);
glm::vec2 uv3 = glm::vec2(u1, v2);
addVertex(buffer, tip, tan1, uv1);
addVertex(buffer, pos2, tan2, uv2);
addVertex(buffer, pos3, tan3, uv3);
_numVertices += 3;
}
}
int MaterialEntityRenderer::_numVertices = 0;
std::shared_ptr<gpu::Stream::Format> MaterialEntityRenderer::_streamFormat = nullptr;
std::shared_ptr<gpu::BufferStream> MaterialEntityRenderer::_stream = nullptr;
std::shared_ptr<gpu::Buffer> MaterialEntityRenderer::_verticesBuffer = nullptr;
void MaterialEntityRenderer::generateMesh() {
_streamFormat = std::make_shared<gpu::Stream::Format>();
_stream = std::make_shared<gpu::BufferStream>();
_verticesBuffer = std::make_shared<gpu::Buffer>();
const int NUM_POS_COORDS = 3;
const int NUM_TANGENT_COORDS = 3;
const int VERTEX_TANGENT_OFFSET = NUM_POS_COORDS * sizeof(float);
const int VERTEX_TEXCOORD_OFFSET = VERTEX_TANGENT_OFFSET + NUM_TANGENT_COORDS * sizeof(float);
_streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
_streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
_streamFormat->setAttribute(gpu::Stream::TANGENT, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_TANGENT_OFFSET);
_streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
_stream->addBuffer(_verticesBuffer, 0, _streamFormat->getChannels().at(0)._stride);
std::vector<float> vertexBuffer;
// Top
addTriangleFan(vertexBuffer, 0, 1);
// Middle section
for (int j = 1; j < STACKS - 1; j++) {
float v1 = ((float)j) / STACKS;
float v2 = ((float)(j + 1)) / STACKS;
float theta1 = v1 * (float)M_PI;
float theta2 = v2 * (float)M_PI;
for (int i = 0; i < SLICES; i++) {
float u1 = ((float)i) / SLICES;
float u2 = ((float)(i + 1)) / SLICES;
float phi1 = u1 * M_PI_TIMES_2;
float phi2 = u2 * M_PI_TIMES_2;
/*
p2 ---- p3
| / |
| / |
| / |
p1 ---- p4
*/
glm::vec3 pos1 = getVertexPos(phi1, theta2);
glm::vec3 pos2 = getVertexPos(phi1, theta1);
glm::vec3 pos3 = getVertexPos(phi2, theta1);
glm::vec3 pos4 = getVertexPos(phi2, theta2);
glm::vec3 tan1 = getTangent(phi1, theta2);
glm::vec3 tan2 = getTangent(phi1, theta1);
glm::vec3 tan3 = getTangent(phi2, theta1);
glm::vec3 tan4 = getTangent(phi2, theta2);
glm::vec2 uv1 = glm::vec2(u1, v2);
glm::vec2 uv2 = glm::vec2(u1, v1);
glm::vec2 uv3 = glm::vec2(u2, v1);
glm::vec2 uv4 = glm::vec2(u2, v2);
addVertex(vertexBuffer, pos1, tan1, uv1);
addVertex(vertexBuffer, pos2, tan2, uv2);
addVertex(vertexBuffer, pos3, tan3, uv3);
addVertex(vertexBuffer, pos3, tan3, uv3);
addVertex(vertexBuffer, pos4, tan4, uv4);
addVertex(vertexBuffer, pos1, tan1, uv1);
_numVertices += 6;
}
}
// Bottom
addTriangleFan(vertexBuffer, STACKS, -1);
_verticesBuffer->append(vertexBuffer.size() * sizeof(float), (gpu::Byte*) vertexBuffer.data());
}
void MaterialEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableMaterialEntityItem::render");
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
// Don't render if our parent is set or our material is null
QUuid parentID;
Transform renderTransform;
graphics::MaterialPointer drawMaterial;
Transform textureTransform;
withReadLock([&] {
parentID = _clientOnly ? _owningAvatarID : _parentID;
renderTransform = _renderTransform;
drawMaterial = _drawMaterial;
textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0));
textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot)));
textureTransform.setScale(glm::vec3(_materialMappingScale, 1));
});
if (!parentID.isNull() || !drawMaterial) {
return;
}
batch.setModelTransform(renderTransform);
drawMaterial->setTextureTransforms(textureTransform);
// bind the material
RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing);
args->_details._materialSwitches++;
// Draw!
if (_numVertices == 0) {
generateMesh();
}
batch.setInputFormat(_streamFormat);
batch.setInputStream(0, *_stream);
batch.draw(gpu::TRIANGLES, _numVertices, 0);
const int NUM_VERTICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _numVertices / NUM_VERTICES_PER_TRIANGLE;
}

View file

@ -0,0 +1,60 @@
//
// Created by Sam Gondelman on 1/18/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RenderableMaterialEntityItem_h
#define hifi_RenderableMaterialEntityItem_h
#include "RenderableEntityItem.h"
#include <MaterialEntityItem.h>
class NetworkMaterial;
namespace render { namespace entities {
class MaterialEntityRenderer : public TypedEntityRenderer<MaterialEntityItem> {
using Parent = TypedEntityRenderer<MaterialEntityItem>;
using Pointer = std::shared_ptr<MaterialEntityRenderer>;
public:
MaterialEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {}
private:
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;
ItemKey getKey() override;
ShapeKey getShapeKey() override;
QUuid _parentID;
bool _clientOnly;
QUuid _owningAvatarID;
glm::vec2 _materialMappingPos;
glm::vec2 _materialMappingScale;
float _materialMappingRot;
Transform _renderTransform;
std::shared_ptr<NetworkMaterial> _drawMaterial;
static int _numVertices;
static std::shared_ptr<gpu::Stream::Format> _streamFormat;
static std::shared_ptr<gpu::BufferStream> _stream;
static std::shared_ptr<gpu::Buffer> _verticesBuffer;
void generateMesh();
void addTriangleFan(std::vector<float>& buffer, int stack, int step);
static glm::vec3 getVertexPos(float phi, float theta);
static glm::vec3 getTangent(float phi, float theta);
static void addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv);
const int SLICES = 15;
const int STACKS = 9;
const float M_PI_TIMES_2 = 2.0f * (float)M_PI;
};
} }
#endif // hifi_RenderableMaterialEntityItem_h

View file

@ -985,6 +985,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() {
return;
}
bool changed { false };
// relay any inbound joint changes from scripts/animation/network to the model/rig
_jointDataLock.withWriteLock([&] {
for (int index = 0; index < _localJointData.size(); ++index) {
@ -992,13 +993,21 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() {
if (jointData.rotationDirty) {
model->setJointRotation(index, true, jointData.joint.rotation, 1.0f);
jointData.rotationDirty = false;
changed = true;
}
if (jointData.translationDirty) {
model->setJointTranslation(index, true, jointData.joint.translation, 1.0f);
jointData.translationDirty = false;
changed = true;
}
}
});
if (changed) {
forEachChild([&](SpatiallyNestablePointer object) {
object->locationChanged(false);
});
}
}
using namespace render;
@ -1343,7 +1352,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
// the renderable item. As it stands now the model checks it's visible/invisible state
// so most of the time we don't do anything in this function.
model->setVisibleInScene(_visible, scene, viewTaskBits);
model->setVisibleInScene(_visible, scene, viewTaskBits, false);
}
// TODO? early exit here when not visible?
@ -1380,6 +1389,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
auto entityRenderer = static_cast<EntityRenderer*>(&data);
entityRenderer->setSubRenderItemIDs(newRenderItemIDs);
});
processMaterials();
}
}
@ -1466,3 +1476,28 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStr
}
}
void ModelEntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
Parent::addMaterial(material, parentMaterialName);
if (_model && _model->fetchRenderItemIDs().size() > 0) {
_model->addMaterial(material, parentMaterialName);
}
}
void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
Parent::removeMaterial(material, parentMaterialName);
if (_model && _model->fetchRenderItemIDs().size() > 0) {
_model->removeMaterial(material, parentMaterialName);
}
}
void ModelEntityRenderer::processMaterials() {
assert(_model);
std::lock_guard<std::mutex> lock(_materialsLock);
for (auto& shapeMaterialPair : _materials) {
auto material = shapeMaterialPair.second;
while (!material.empty()) {
_model->addMaterial(material.top(), shapeMaterialPair.first);
material.pop();
}
}
}

View file

@ -138,10 +138,14 @@ namespace render { namespace entities {
class ModelEntityRenderer : public TypedEntityRenderer<RenderableModelEntityItem> {
using Parent = TypedEntityRenderer<RenderableModelEntityItem>;
friend class EntityRenderer;
Q_OBJECT
public:
ModelEntityRenderer(const EntityItemPointer& entity);
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
protected:
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
@ -194,6 +198,8 @@ private:
uint64_t _lastAnimated { 0 };
render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() };
void processMaterials();
};
} } // namespace

Some files were not shown because too many files have changed in this diff Show more