Merge remote-tracking branch 'upstream/master' into parenting

This commit is contained in:
HifiExperiments 2020-07-01 14:56:36 -07:00
commit 37ce2996c8
73 changed files with 2132 additions and 557 deletions

View file

@ -1,13 +1,13 @@
# General Build Information
*Last Updated on May 17, 2020*
*Last Updated on June 27, 2020*
### OS Specific Build Guides
* [Build Windows](BUILD_WIN.md) - complete instructions for Windows.
* [Build Linux](BUILD_LINUX.md) - additional instructions for Linux.
* [Build OSX](BUILD_OSX.md) - additional instructions for OS X.
* [Build Android](BUILD_ANDROID.md) - additional instructions for Android
* [Build Android](BUILD_ANDROID.md) - additional instructions for Android.
### Dependencies
- [git](https://git-scm.com/downloads): >= 1.6
@ -78,11 +78,11 @@ Where /path/to/directory is the path to a directory where you wish the build fil
BUILD_NUMBER
// The type of release.
RELEASE_TYPE=PRODUCTION|PR
RELEASE_BUILD=PRODUCTION|PR
RELEASE_TYPE=PRODUCTION|PR|DEV
// TODO: What do these do?
PRODUCTION_BUILD=0|1
PR_BUILD=0|1
STABLE_BUILD=0|1
// TODO: What do these do?
@ -150,4 +150,4 @@ The following build options can be used when running CMake
#### Devices
You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device.

View file

@ -176,7 +176,7 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
} else if (loaded && meta.failedLastBake) {
return { AssetUtils::Error, meta.lastBakeErrors };
}
return { AssetUtils::Pending, "" };
}
@ -199,7 +199,7 @@ void AssetServer::maybeBake(const AssetUtils::AssetPath& path, const AssetUtils:
void AssetServer::createEmptyMetaFile(const AssetUtils::AssetHash& hash) {
QString metaFilePath = "atp:/" + hash + "/meta.json";
QFile metaFile { metaFilePath };
if (!metaFile.exists()) {
qDebug() << "Creating metafile for " << hash;
if (metaFile.open(QFile::WriteOnly)) {
@ -285,7 +285,7 @@ void updateConsumedCores() {
auto coreCount = std::thread::hardware_concurrency();
if (isInterfaceRunning) {
coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW;
}
}
qCDebug(asset_server) << "Setting max consumed cores to " << coreCount;
setMaxCores(coreCount);
}
@ -931,6 +931,9 @@ void AssetServer::sendStatsPacket() {
connectionStats["5. Period (us)"] = stats.packetSendPeriod;
connectionStats["6. Up (Mb/s)"] = stats.sentBytes * megabitsPerSecPerByte;
connectionStats["7. Down (Mb/s)"] = stats.receivedBytes * megabitsPerSecPerByte;
connectionStats["last_heard_time_msecs"] = date.toUTC().toMSecsSinceEpoch();
connectionStats["last_heard_ago_msecs"] = date.msecsTo(QDateTime::currentDateTime());
nodeStats["Connection Stats"] = connectionStats;
using Events = udt::ConnectionStats::Stats::Event;
@ -1147,7 +1150,7 @@ bool AssetServer::deleteMappings(const AssetUtils::AssetPathList& paths) {
hashesToCheckForDeletion << it->second;
qCDebug(asset_server) << "Deleted a mapping:" << path << "=>" << it->second;
_fileMappings.erase(it);
} else {
qCDebug(asset_server) << "Unable to delete a mapping that was not found:" << path;

View file

@ -668,6 +668,12 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
downstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
downstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
downstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
downstreamStats["min_gap_usecs"] = static_cast<double>(streamStats._timeGapMin);
downstreamStats["max_gap_usecs"] = static_cast<double>(streamStats._timeGapMax);
downstreamStats["avg_gap_usecs"] = static_cast<double>(streamStats._timeGapAverage);
downstreamStats["min_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMin);
downstreamStats["max_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMax);
downstreamStats["avg_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowAverage);
result["downstream"] = downstreamStats;
@ -695,6 +701,13 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
upstreamStats["min_gap_usecs"] = static_cast<double>(streamStats._timeGapMin);
upstreamStats["max_gap_usecs"] = static_cast<double>(streamStats._timeGapMax);
upstreamStats["avg_gap_usecs"] = static_cast<double>(streamStats._timeGapAverage);
upstreamStats["min_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMin);
upstreamStats["max_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMax);
upstreamStats["avg_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowAverage);
result["upstream"] = upstreamStats;
} else {
result["upstream"] = "mic unknown";
@ -725,6 +738,12 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
upstreamStats["min_gap_usecs"] = static_cast<double>(streamStats._timeGapMin);
upstreamStats["max_gap_usecs"] = static_cast<double>(streamStats._timeGapMax);
upstreamStats["avg_gap_usecs"] = static_cast<double>(streamStats._timeGapAverage);
upstreamStats["min_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMin);
upstreamStats["max_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMax);
upstreamStats["avg_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowAverage);
injectorArray.push_back(upstreamStats);
}
}

View file

@ -89,6 +89,7 @@ int OctreeServer::_shortProcessWait = 0;
int OctreeServer::_noProcessWait = 0;
static const QString PERSIST_FILE_DOWNLOAD_PATH = "/models.json.gz";
static const double NANOSECONDS_PER_SECOND = 1000000.0;;
void OctreeServer::resetSendingStats() {
@ -1197,7 +1198,7 @@ void OctreeServer::domainSettingsRequestComplete() {
} else {
beginRunning();
}
}
}
void OctreeServer::beginRunning() {
auto nodeList = DependencyManager::get<NodeList>();
@ -1344,6 +1345,10 @@ QString OctreeServer::getUptime() {
return formattedUptime;
}
double OctreeServer::getUptimeSeconds() {
return (usecTimestampNow() - _startedUSecs) / NANOSECONDS_PER_SECOND;
}
QString OctreeServer::getFileLoadTime() {
QString result;
if (isInitialLoadComplete()) {
@ -1386,6 +1391,10 @@ QString OctreeServer::getFileLoadTime() {
return result;
}
double OctreeServer::getFileLoadTimeSeconds() {
return getLoadElapsedTime() / NANOSECONDS_PER_SECOND;
}
QString OctreeServer::getConfiguration() {
QString result;
for (int i = 1; i < _argc; i++) {
@ -1421,6 +1430,8 @@ void OctreeServer::sendStatsPacket() {
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
statsArray1["5. clients"] = getCurrentClientCount();
statsArray1["6. threads"] = threadsStats;
statsArray1["uptime_seconds"] = getUptimeSeconds();
statsArray1["persistFileLoadTime_seconds"] = getFileLoadTimeSeconds();
// Octree Stats
QJsonObject octreeStats;

View file

@ -158,7 +158,9 @@ protected:
void initHTTPManager(int port);
void resetSendingStats();
QString getUptime();
double getUptimeSeconds();
QString getFileLoadTime();
double getFileLoadTimeSeconds();
QString getConfiguration();
QString getStatusLink();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View file

@ -2,10 +2,12 @@ include(vcpkg_common_functions)
set(ARISTO_VERSION 0.8.1)
set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src)
file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
if (WIN32)
vcpkg_download_distfile(
ARISTO_SOURCE_ARCHIVE
URLS https://athena-public.s3.amazonaws.com/seth/aristo-0.8.1-windows.zip
URLS "${EXTERNAL_BUILD_ASSETS}/seth/aristo-0.8.1-windows.zip"
SHA512 05179c63b72a1c9f5be8a7a2b7389025da683400dbf819e5a6199dd6473c56774d2885182dc5a11cb6324058d228a4ead832222e8b3e1bebaa4c61982e85f0a8
FILENAME aristo-0.8.1-windows.zip
)

View file

@ -1,3 +1,3 @@
Source: sranipal
Version: 1.1.0.1
Description: SRanipal
Description: super reality animation pal!

View file

@ -2,10 +2,12 @@ include(vcpkg_common_functions)
set(SRANIPAL_VERSION 1.1.0.1)
set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src)
file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
if (WIN32)
vcpkg_download_distfile(
SRANIPAL_SOURCE_ARCHIVE
URLS https://athena-public.s3.amazonaws.com/seth/sranipal-1.1.0.1-windows.zip
URLS "${EXTERNAL_BUILD_ASSETS}/seth/sranipal-1.1.0.1-windows.zip"
SHA512 b09ce012abe4e3c71e8e69626bdd7823ff6576601a821ab365275f2764406a3e5f7b65fcf2eb1d0962eff31eb5958a148b00901f67c229dc6ace56eb5e6c9e1b
FILENAME sranipal-1.1.0.1-windows.zip
)

View file

@ -57,6 +57,29 @@
}
]
},
{
"label": "Monitoring",
"name": "monitoring",
"restart": false,
"settings": [
{
"name": "enable_prometheus_exporter",
"label": "Enable Prometheus Exporter",
"help": "Enable a Prometheus exporter to make it possible to gather the stats that are available at <a href='/'>Nodes</a> tab with a <a href='https://prometheus.io/'>Prometheus</a> server. This makes it possible to keep track of long-term domain statistics for graphing, troubleshooting, and performance monitoring.",
"default": false,
"type": "checkbox",
"advanced": true
},
{
"name": "prometheus_exporter_port",
"label": "Prometheus TCP Port",
"help": "This is the port where the Prometheus exporter accepts connections. It listens both on IPv4 and IPv6 and can be accessed remotely, so you should make sure to restrict access with a firewall as needed.",
"default": "9703",
"type": "int",
"advanced": true
}
]
},
{
"label": "Paths",
"html_id": "paths",

View file

@ -0,0 +1,14 @@
<html>
<head>
<title>Vircadia Prometheus exporter</title>
</head>
<body>
<h1>Vircadia Prometheus exporter</h1>
<p>This is the <a href="https://prometheus.io/">Prometheus</a> exporter, used to export stats about the domain server for graphing and analysis.</p>
<p>
<a href="/metrics">Metrics</a>
</p>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -52,6 +52,7 @@ var URLs = {
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
// STAGING METAVERSE_URL: https://staging.highfidelity.com
METAVERSE_URL: 'https://metaverse.highfidelity.com',
CDN_URL: 'https://cdn.highfidelity.com',
PLACE_URL: 'https://hifi.place',
};

View file

@ -67,16 +67,13 @@ Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";
const int MIN_PORT = 1;
const int MAX_PORT = 65535;
int const DomainServer::EXIT_CODE_REBOOT = 234923;
#if USE_STABLE_GLOBAL_SERVICES
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
#else
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
#endif
QString DomainServer::_iceServerAddr { ICE_SERVER_DEFAULT_HOSTNAME };
QString DomainServer::_iceServerAddr { NetworkingConstants::ICE_SERVER_DEFAULT_HOSTNAME };
int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT };
bool DomainServer::_overrideDomainID { false };
QUuid DomainServer::_overridingDomainID;
@ -229,7 +226,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
this, &DomainServer::updateDownstreamNodes);
connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,
this, &DomainServer::updateUpstreamNodes);
setupGroupCacheRefresh();
optionallySetupOAuth();
@ -330,6 +326,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_nodePingMonitorTimer = new QTimer{ this };
connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor);
_nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS);
initializeExporter();
}
void DomainServer::parseCommandLine(int argc, char* argv[]) {
@ -424,6 +422,11 @@ DomainServer::~DomainServer() {
_contentManager->terminate();
}
if (_httpExporterManager) {
_httpExporterManager->close();
delete _httpExporterManager;
}
DependencyManager::destroy<AccountManager>();
// cleanup the AssetClient thread
@ -1977,7 +1980,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_API_BACKUPS_ID = "/api/backups/";
const QString URI_API_BACKUPS_DOWNLOAD_ID = "/api/backups/download/";
const QString URI_API_BACKUPS_RECOVER = "/api/backups/recover/";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
QPointer<HTTPConnection> connectionPtr { connection };
@ -3037,6 +3039,27 @@ void DomainServer::updateUpstreamNodes() {
updateReplicationNodes(Upstream);
}
void DomainServer::initializeExporter()
{
static const QString ENABLE_EXPORTER = "monitoring.enable_prometheus_exporter";
static const QString EXPORTER_PORT = "monitoring.prometheus_exporter_port";
bool isExporterEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_EXPORTER).toBool();
int exporterPort = _settingsManager.valueOrDefaultValueForKeyPath(EXPORTER_PORT).toInt();
if (exporterPort < MIN_PORT || exporterPort > MAX_PORT) {
qCWarning(domain_server) << "Prometheus exporter port " << exporterPort << " is out of range.";
isExporterEnabled = false;
}
qCDebug(domain_server) << "Setting up Prometheus exporter";
if (isExporterEnabled && !_httpExporterManager) {
qCInfo(domain_server) << "Starting Prometheus exporter on port " << exporterPort;
_httpExporterManager = new HTTPManager(QHostAddress::Any, (quint16)exporterPort, QString("%1/resources/prometheus_exporter/").arg(QCoreApplication::applicationDirPath()), &_exporter);
}
}
void DomainServer::updateReplicatedNodes() {
// Make sure we have downstream nodes in our list
static const QString REPLICATED_USERS_KEY = "users";

View file

@ -36,6 +36,7 @@
#include "DomainContentBackupManager.h"
#include "PendingAssignedNodeData.h"
#include "DomainServerExporter.h"
#include <QLoggingCategory>
@ -115,7 +116,7 @@ private slots:
void sendHeartbeatToIceServer();
void nodePingMonitor();
void handleConnectedNode(SharedNodePointer newNode, quint64 requestReceiveTime);
void handleConnectedNode(SharedNodePointer newNode, quint64 requestReceiveTime);
void handleTempDomainSuccess(QNetworkReply* requestReply);
void handleTempDomainError(QNetworkReply* requestReply);
@ -138,6 +139,7 @@ private slots:
void updateReplicatedNodes();
void updateDownstreamNodes();
void updateUpstreamNodes();
void initializeExporter();
void tokenGrantFinished();
void profileRequestFinished();
@ -234,8 +236,10 @@ private:
std::vector<QString> _replicatedUsernames;
DomainGatekeeper _gatekeeper;
DomainServerExporter _exporter;
HTTPManager _httpManager;
HTTPManager* _httpExporterManager { nullptr };
std::unique_ptr<HTTPSManager> _httpsManager;
QHash<QUuid, SharedAssignmentPointer> _allAssignments;

View file

@ -0,0 +1,471 @@
//
// DomainServerExporter.cpp
// domain-server/src
//
// Created by Dale Glass on 3 Apr 2020.
// Copyright 2020 Dale Glass
//
// Prometheus exporter
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// TODO:
//
// Look into the data provided by OctreeServer::handleHTTPRequest in assignment-client/src/octree/OctreeServer.cpp
// Turns out the octree server (entity server) can optionally deliver additional statistics via another HTTP server
// that is disabled by default. This functionality can be enabled by setting statusPort to a port number.
//
// Look into what appears in Audio Mixer -> z_listeners -> jitter -> injectors, so far it's been an empty list.
#include <QLoggingCategory>
#include <QUrl>
#include <QJsonObject>
#include <QJsonDocument>
#include <QRegularExpression>
#include <QSet>
#include "DomainServerExporter.h"
#include "DependencyManager.h"
#include "LimitedNodeList.h"
#include "HTTPConnection.h"
#include "DomainServerNodeData.h"
Q_LOGGING_CATEGORY(domain_server_exporter, "hifi.domain_server.prometheus_exporter")
static const QMap<QString, DomainServerExporter::MetricType> TYPE_MAP {
{ "asset_server_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_cw_p" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_down_mb_s" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_est_max_p_s" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_last_heard_ago_msecs" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_last_heard_time_msecs" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_period_us" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_rtt_ms" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_connection_stats_up_mb_s" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_downstream_stats_duplicates" , DomainServerExporter::MetricType::Counter },
{ "asset_server_downstream_stats_recvd_p_s" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_downstream_stats_recvd_packets" , DomainServerExporter::MetricType::Counter },
{ "asset_server_downstream_stats_sent_ack" , DomainServerExporter::MetricType::Counter },
{ "asset_server_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_upstream_stats_procd_ack" , DomainServerExporter::MetricType::Counter },
{ "asset_server_upstream_stats_recvd_ack" , DomainServerExporter::MetricType::Counter },
{ "asset_server_upstream_stats_retransmitted" , DomainServerExporter::MetricType::Counter },
{ "asset_server_upstream_stats_sent_p_s" , DomainServerExporter::MetricType::Gauge },
{ "asset_server_upstream_stats_sent_packets" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_listeners_per_frame" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_listeners_silent_per_frame" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_streams_per_frame" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_check_time" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_check_time_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_events" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_events_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_frame" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_frame_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_mix" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_mix_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_packets" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_packets_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_sleep" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_sleep_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_tic" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_avg_timing_stats_us_per_tic_trailing" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_available" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_available_avg_10s" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_avg_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_avg_gap_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_desired" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_lost_percent" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_lost_percent_30s" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_max_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_max_gap_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_min_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_min_gap_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_downstream_not_mixed" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_downstream_overflows" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_downstream_starves" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_downstream_unplayed" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_injectors" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_upstream_available" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_available_avg_10s" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_avg_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_avg_gap_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_desired_calc" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_lost_percent" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_lost_percent_30s" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_max_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_max_gap_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_mic_desired" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_min_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_min_gap_usecs" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_listeners_jitter_upstream_not_mixed" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_upstream_overflows" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_upstream_silents_dropped" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_upstream_starves" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_jitter_upstream_unplayed" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_listeners_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_active_streams" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_active_to_inactive" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_active_to_skippped" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_avg_mixes_per_block" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_hrtf_renders" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_hrtf_resets" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_hrtf_updates" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_inactive_streams" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_inactive_to_active" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_inactive_to_skippped" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_percent_hrtf_mixes" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_percent_manual_echo_mixes" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_percent_manual_stereo_mixes" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_mix_stats_skipped_streams" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_skippped_to_active" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_skippped_to_inactive" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_mix_stats_total_mixes" , DomainServerExporter::MetricType::Counter },
{ "audio_mixer_silent_packets_per_frame" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_threads" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_throttling_ratio" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_trailing_mix_ratio" , DomainServerExporter::MetricType::Gauge },
{ "audio_mixer_use_dynamic_jitter_buffers" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_av_data_receive_rate" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_avg_other_av_skips_per_second" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_avg_other_av_starves_per_second" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_delta_full_vs_avatar_data_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_inbound_av_data_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_num_avs_sent_last_frame" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_outbound_av_data_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_outbound_av_traits_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_recent_other_av_in_view" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_recent_other_av_out_of_view" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_avatars_total_num_out_of_order_sends" , DomainServerExporter::MetricType::Counter },
{ "avatar_mixer_average_listeners_last_second" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_broadcast_loop_rate" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_functor" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_innner" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_lock_wait" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_node_transform" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_total" , DomainServerExporter::MetricType::Counter },
{ "avatar_mixer_parallel_tasks_display_name_management_total" , DomainServerExporter::MetricType::Counter },
{ "avatar_mixer_parallel_tasks_process_queued_avatar_data_packets_lock_wait" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_parallel_tasks_process_queued_avatar_data_packets_total" , DomainServerExporter::MetricType::Counter },
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_avatar_identity_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_avatar_query_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_kill_avatar_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_node_ignore_request_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_radius_ignore_request_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_requests_domain_list_data_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_process_events" , DomainServerExporter::MetricType::Counter },
{ "avatar_mixer_single_core_tasks_queue_incoming_packet" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_single_core_tasks_send_stats" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_received_1_nodes_processed" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_1_nodes_broadcasted_to" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_2_average_others_included" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_3_average_over_budget_avatars" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_4_average_data_bytes" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_5_average_traits_bytes" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_6_average_identity_bytes" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_sent_7_average_hero_avatars" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_timing_1_process_incoming_packets" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_timing_2_ignore_calculation" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_timing_3_to_byte_array" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_timing_4_avatar_data_packing" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_timing_5_packet_sending" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_slaves_aggregate_per_frame_timing_6_job_elapsed_time" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_threads" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_throttling_ratio" , DomainServerExporter::MetricType::Gauge },
{ "avatar_mixer_trailing_mix_ratio" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_nodes_inbound_kbit_s" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_nodes_outbound_kbit_s" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_nodes_reliable_packet_s" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_nodes_unreliable_packet_s" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_octree_stats_element_count" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_octree_stats_internal_element_count" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_octree_stats_leaf_element_count" , DomainServerExporter::MetricType::Gauge },
{ "entity_script_server_script_engine_stats_number_running_scripts" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_data_packet_queue" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_data_total_elements" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_data_total_packets" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_timing_avg_lock_wait_time_per_element" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_timing_avg_lock_wait_time_per_packet" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_timing_avg_process_time_per_element" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_timing_avg_process_time_per_packet" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_inbound_timing_avg_transit_time_per_packet" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_clients" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_persist_file_load_time_seconds" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_threads_handle_pacekt_send" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_threads_packet_distributor" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_threads_processing" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_threads_write_datagram" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_misc_uptime_seconds" , DomainServerExporter::MetricType::Counter },
{ "entity_server_entity_server_octree_element_count" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_octree_internal_element_count" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_octree_leaf_element_count" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_data_total_bytes" , DomainServerExporter::MetricType::Counter },
{ "entity_server_entity_server_outbound_data_total_bytes_bit_masks" , DomainServerExporter::MetricType::Counter },
{ "entity_server_entity_server_outbound_data_total_bytes_octal_codes" , DomainServerExporter::MetricType::Counter },
{ "entity_server_entity_server_outbound_data_total_bytes_wasted" , DomainServerExporter::MetricType::Counter },
{ "entity_server_entity_server_outbound_data_total_packets" , DomainServerExporter::MetricType::Counter },
{ "entity_server_entity_server_outbound_timing_avg_compress_and_write_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_timing_avg_encode_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_timing_avg_inside_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_timing_avg_loop_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_timing_avg_send_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_timing_avg_tree_traverse_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_entity_server_outbound_timing_node_wait_time" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "entity_server_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_messages_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
{ "messages_mixer_messages_outbound_kbps" , DomainServerExporter::MetricType::Gauge }
};
// Things we're not going to convert for various reasons, such as containing text,
// or having a value followed by an unit ("5.2 seconds").
//
// Things like text like usernames have no place in the Prometheus model, so they can be skipped.
//
// For numeric values with an unit, instead of trying to parse it, the stats will just need to
// have a second copy of the metric added, with the value expressed as a number, with the original
// being blacklisted here.
static const QSet<QString> BLACKLIST = {
"asset_server_connection_stats_last_heard", // Timestamp as a string
"asset_server_username", // Username
"audio_mixer_listeners_jitter_downstream_avg_gap", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_downstream_avg_gap_30s", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_downstream_max_gap", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_downstream_max_gap_30s", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_downstream_min_gap", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_downstream_min_gap_30s", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_injectors", // Array, empty. TODO: check if this ever contains anything.
"audio_mixer_listeners_jitter_upstream", // Only exists in the absence of a connection
"audio_mixer_listeners_jitter_upstream_avg_gap", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_upstream_avg_gap_30s", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_upstream_max_gap", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_upstream_max_gap_30s", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_upstream_min_gap", // Number as string with unit name, alternative added
"audio_mixer_listeners_jitter_upstream_min_gap_30s", // Number as string with unit name, alternative added
"audio_mixer_listeners_username", // Username
"avatar_mixer_avatars_display_name", // Username
"avatar_mixer_avatars_username", // Username
"entity_script_server_nodes_node_type", // Username
"entity_script_server_nodes_username", // Username
"entity_server_entity_server_misc_configuration", // Text
"entity_server_entity_server_misc_detailed_stats_url", // URL
"entity_server_entity_server_misc_persist_file_load_time", // Number as string with unit name, alternative added
"entity_server_entity_server_misc_uptime", // Number as string with unit name, alternative added
"messages_mixer_messages_username" // Username
};
DomainServerExporter::DomainServerExporter() {
}
bool DomainServerExporter::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
const QString URI_METRICS = "/metrics";
const QString EXPORTER_MIME_TYPE = "text/plain";
qCDebug(domain_server_exporter) << "Request on URL " << url;
if (url.path() == URI_METRICS) {
auto nodeList = DependencyManager::get<LimitedNodeList>();
QString output = "";
QTextStream outStream(&output);
nodeList->eachNode([this, &outStream](const SharedNodePointer& node) { generateMetricsForNode(outStream, node); });
connection->respond(HTTPConnection::StatusCode200, output.toUtf8(), qPrintable(EXPORTER_MIME_TYPE));
return true;
}
return false;
}
QString DomainServerExporter::escapeName(const QString& name) {
QRegularExpression invalidCharacters("[^A-Za-z0-9_]");
QString result = name;
// If a key is named something like: "6. threads", turn it into just "threads"
result.replace(QRegularExpression("^\\d+\\. "), "");
result.replace(QRegularExpression("^\\d+_"), "");
// If a key is named something like "z_listeners", turn it into just "listeners"
result.replace(QRegularExpression("^z_"), "");
// If a key is named something like "lost%", change it to "lost_percent_".
// redundant underscores will be removed below.
result.replace(QRegularExpression("%"), "_percent_");
// change mixedCaseNames to mixed_case_names
result.replace(QRegularExpression("([a-z])([A-Z])"), "\\1_\\2");
// Replace all invalid characters with a _
result.replace(invalidCharacters, "_");
// Remove any "_" characters at the beginning or end
result.replace(QRegularExpression("^_+"), "");
result.replace(QRegularExpression("_+$"), "");
// Replace any duplicated _ characters with a single one
result.replace(QRegularExpression("_+"), "_");
result = result.toLower();
return result;
}
void DomainServerExporter::generateMetricsForNode(QTextStream& stream, const SharedNodePointer& node) {
QJsonObject statsObject = static_cast<DomainServerNodeData*>(node->getLinkedData())->getStatsJSONObject();
QString nodeType = NodeType::getNodeTypeName(static_cast<NodeType_t>(node->getType()));
stream << "\n\n\n";
stream << "###############################################################\n";
stream << "# " << nodeType << "\n";
stream << "###############################################################\n";
generateMetricsFromJson(stream, nodeType, escapeName(nodeType), QHash<QString, QString>(), statsObject);
}
void DomainServerExporter::generateMetricsFromJson(QTextStream& stream,
QString originalPath,
QString path,
QHash<QString, QString> labels,
const QJsonObject& root) {
for (auto iter = root.constBegin(); iter != root.constEnd(); ++iter) {
auto escapedKey = escapeName(iter.key());
auto metricValue = iter.value();
auto metricName = path + "_" + escapedKey;
auto origMetricName = originalPath + " -> " + iter.key();
if (metricValue.isObject()) {
QUuid possible_uuid = QUuid::fromString(iter.key());
if (possible_uuid.isNull()) {
generateMetricsFromJson(stream, originalPath + " -> " + iter.key(), path + "_" + escapedKey, labels,
iter.value().toObject());
} else {
labels.insert("uuid", possible_uuid.toString(QUuid::WithoutBraces));
generateMetricsFromJson(stream, originalPath, path, labels, iter.value().toObject());
}
continue;
}
if (BLACKLIST.contains(metricName)) {
continue;
}
bool conversionOk = false;
double converted = 0;
if (metricValue.isString()) {
// Prometheus only deals with numeric values. See if this string contains a valid one
QString tmp = metricValue.toString();
converted = tmp.toDouble(&conversionOk);
if (!conversionOk) {
qCWarning(domain_server_exporter) << "Failed to convert value of " << origMetricName << " (" << metricName
<< ") to double: " << tmp << "'";
continue;
}
}
stream << QString("\n# HELP %1 %2 -> %3\n").arg(metricName).arg(originalPath).arg(iter.key());
if (TYPE_MAP.contains(metricName)) {
stream << "# TYPE " << metricName << " ";
switch (TYPE_MAP[metricName]) {
case DomainServerExporter::MetricType::Untyped:
stream << "untyped";
break;
case DomainServerExporter::MetricType::Counter:
stream << "counter";
break;
case DomainServerExporter::MetricType::Gauge:
stream << "gauge";
break;
case DomainServerExporter::MetricType::Histogram:
stream << "histogram";
break;
case DomainServerExporter::MetricType::Summary:
stream << "summary";
break;
}
stream << "\n";
} else {
qCWarning(domain_server_exporter)
<< "Type for metric " << origMetricName << " (" << metricName << ") not known.";
}
stream << path << "_" << escapedKey;
if (!labels.isEmpty()) {
stream << "{";
bool isFirst = true;
QHashIterator<QString, QString> iter(labels);
while (iter.hasNext()) {
iter.next();
if (!isFirst) {
stream << ",";
}
QString escapedValue = iter.value();
escapedValue.replace("\\", "\\\\");
escapedValue.replace("\"", "\\\"");
escapedValue.replace("\n", "\\\n");
stream << iter.key() << "=\"" << escapedValue << "\"";
isFirst = false;
}
stream << "}";
}
stream << " ";
if (metricValue.isBool()) {
stream << (iter.value().toBool() ? "1" : "0");
} else if (metricValue.isDouble()) {
stream << metricValue.toDouble();
} else if (metricValue.isString()) {
// Converted above
stream << converted;
} else {
qCWarning(domain_server_exporter)
<< "Can't convert metric " << origMetricName << "(" << metricName << ") with value " << metricValue;
}
stream << "\n";
}
}

View file

@ -0,0 +1,55 @@
//
// DomainServerExporter.h
// domain-server/src
//
// Created by Dale Glass on 3 Apr 2020.
// Copyright 2020 Dale Glass
//
// Prometheus exporter
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef DOMAINSERVEREXPORTER_H
#define DOMAINSERVEREXPORTER_H
#include <QObject>
#include "HTTPManager.h"
#include "Node.h"
#include <QTextStream>
#include <QJsonObject>
#include <QRegularExpression>
#include <QHash>
/**
* @brief Prometheus exporter for domain stats
*
* This class exportors the statistics that can be seen on the domain's page in
* a format that can be parsed by Prometheus. This is useful for troubleshooting,
* monitoring performance, and making pretty graphs.
*/
class DomainServerExporter : public HTTPRequestHandler
{
public:
typedef enum {
Untyped, /* Works the same as Gauge, with the difference of signalling that the actual type is unknown */
Counter, /* Value only goes up. Eg, number of packets received */
Gauge, /* Current numerical value that can go up or down. Current temperature, memory usage, etc */
Histogram, /* Samples sorted in buckets gathered over time */
Summary
} MetricType;
DomainServerExporter();
~DomainServerExporter() = default;
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
private:
QString escapeName(const QString &name);
void generateMetricsForNode(QTextStream& stream, const SharedNodePointer& node);
void generateMetricsFromJson(QTextStream& stream, QString originalPath, QString path, QHash<QString, QString> labels, const QJsonObject& obj);
};
#endif // DOMAINSERVEREXPORTER_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 150 150"
enable-background="new 0 0 512 512"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="hifi-logo-blackish.svg"
width="150"
height="150"><metadata
id="metadata57"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs55" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1835"
inkscape:window-height="1057"
id="namedview53"
showgrid="false"
inkscape:zoom="2.6074563"
inkscape:cx="256"
inkscape:cy="67.100056"
inkscape:window-x="77"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" /><g
id="Layer_1"
transform="translate(-30.500027,-42.2)"><g
id="g5"><g
id="g7"><path
d="M 155.3,67.4 C 141.9,54.3 124.5,47 105.8,47 86.8,47 69.4,54.3 56,67.4 42.9,80.8 35.5,98.2 35.5,117.2 c 0,18.7 7.3,36.4 20.5,49.5 13.2,13.1 30.8,20.5 49.5,20.5 18.7,0 36.4,-7.3 49.5,-20.5 13.1,-13.1 20.5,-30.8 20.5,-49.5 0.3,-18.9 -7,-36.4 -20.2,-49.8 z m -5,94.3 c -11.9,11.9 -27.8,18.4 -44.7,18.4 C 88.7,180.1 73,173.5 60.9,161.7 49,149.8 42.5,133.9 42.5,117 c 0,-16.9 6.6,-32.6 18.4,-44.7 11.9,-11.9 27.8,-18.4 44.7,-18.4 16.9,0 32.6,6.6 44.7,18.4 11.9,11.9 18.4,27.8 18.4,44.7 0,16.9 -6.6,32.8 -18.4,44.7 z"
id="path9"
inkscape:connector-curvature="0"
style="fill:#333333" /><g
id="g11"><path
d="m 86.8,142 c -1.5,0 -3,-1.3 -3,-3 l 0,-54.1 c 0,-1.5 1.3,-3 3,-3 1.5,0 3,1.3 3,3 l 0,54.3 c 0.1,1.5 -1.2,2.8 -3,2.8 z"
id="path13"
inkscape:connector-curvature="0"
style="fill:#333333" /><path
d="m 90.4,83.4 c -2,2 -5.1,2 -6.8,0 -2,-2 -2,-5.1 0,-6.8 2,-2 5.1,-2 6.8,0 2,2 2,5 0,6.8"
id="path15"
inkscape:connector-curvature="0"
style="fill:#333333" /><g
id="g17"><path
d="m 86.8,87.9 c -2,0 -4,-0.8 -5.6,-2.3 -3,-3 -3,-8.1 0,-11.1 1.5,-1.5 3.5,-2.3 5.6,-2.3 2.1,0 4,0.8 5.6,2.3 1.5,1.5 2.3,3.5 2.3,5.6 0,2.1 -0.8,4 -2.3,5.6 -1.5,1.2 -3.3,2.2 -5.6,2.2 z m 0,-9.8 c -0.5,0 -1,0.3 -1.5,0.5 -0.8,0.8 -0.8,2 0,2.8 0.8,0.8 2,0.8 2.8,0 0.3,-0.3 0.5,-0.8 0.5,-1.5 0,-0.5 -0.3,-1 -0.5,-1.5 -0.2,-0.1 -0.7,-0.3 -1.3,-0.3 z"
id="path19"
inkscape:connector-curvature="0"
style="fill:#333333" /></g><path
d="m 83.6,140.5 c 2,-2 5.1,-2 6.8,0 2,2 2,5.1 0,6.8 -2,2 -5.1,2 -6.8,0 -2.1,-1.8 -2.1,-4.8 0,-6.8"
id="path21"
inkscape:connector-curvature="0"
style="fill:#333333" /><g
id="g23"><path
d="m 86.8,151.8 c -2,0 -4,-0.8 -5.6,-2.3 C 79.6,148 79,146 79,144 c 0,-2 0.8,-4 2.3,-5.6 1.5,-1.6 3.5,-2.3 5.6,-2.3 2.1,0 4,0.8 5.6,2.3 1.6,1.5 2.3,3.5 2.3,5.6 0,2.1 -0.8,4 -2.3,5.6 -1.6,1.5 -3.4,2.2 -5.7,2.2 z m 0,-9.8 c -0.5,0 -1,0.3 -1.5,0.5 -0.3,0.3 -0.5,0.8 -0.5,1.5 0,0.5 0.3,1 0.5,1.5 0.8,0.8 2,0.8 2.8,0 0.3,-0.3 0.5,-0.8 0.5,-1.5 0,-0.5 -0.3,-1 -0.5,-1.5 -0.2,-0.3 -0.7,-0.5 -1.3,-0.5 z"
id="path25"
inkscape:connector-curvature="0"
style="fill:#333333" /></g><g
id="g27"><path
d="m 124.5,152.3 c -1.5,0 -3,-1.3 -3,-3 l 0,-54.3 c 0,-1.5 1.3,-3 3,-3 1.5,0 3,1.3 3,3 l 0,54.3 c 0,1.8 -1.5,3 -3,3 z"
id="path29"
inkscape:connector-curvature="0"
style="fill:#333333" /></g><path
d="m 128,93.7 c -2,2 -5.1,2 -6.8,0 -2,-2 -2,-5.1 0,-6.8 2,-2 5.1,-2 6.8,0 1.8,1.8 1.8,4.8 0,6.8"
id="path31"
inkscape:connector-curvature="0"
style="fill:#333333" /><g
id="g33"><path
d="m 124.5,98 c -2,0 -4,-0.8 -5.6,-2.3 -3,-3 -3,-8.1 0,-11.1 1.5,-1.5 3.5,-2.3 5.6,-2.3 2,0 4,0.8 5.6,2.3 1.5,1.5 2.3,3.5 2.3,5.6 0,2 -0.8,4 -2.3,5.6 -1.6,1.5 -3.6,2.2 -5.6,2.2 z m 0,-9.8 c -0.5,0 -1,0.3 -1.5,0.5 -0.8,0.8 -0.8,2 0,2.8 0.8,0.8 2,0.8 2.8,0 0.3,-0.3 0.5,-0.8 0.5,-1.5 0,-0.5 -0.3,-1 -0.5,-1.5 -0.3,-0.1 -0.8,-0.3 -1.3,-0.3 z"
id="path35"
inkscape:connector-curvature="0"
style="fill:#333333" /></g><g
id="g37"><path
d="m 124.5,162.2 c -2,0 -4,-0.8 -5.6,-2.3 -3,-3 -3,-8.1 0,-11.1 1.5,-1.5 3.5,-2.3 5.6,-2.3 2,0 4,0.8 5.6,2.3 1.5,1.5 2.3,3.5 2.3,5.6 0,2.1 -0.8,4 -2.3,5.6 -1.6,1.2 -3.6,2.2 -5.6,2.2 z m 0,-9.9 c -0.5,0 -1,0.3 -1.5,0.5 -0.8,0.8 -0.8,2 0,2.8 0.8,0.8 2,0.8 2.8,0 0.3,-0.3 0.5,-0.8 0.5,-1.5 0,-0.7 -0.3,-1 -0.5,-1.5 -0.3,0 -0.8,-0.3 -1.3,-0.3 z"
id="path39"
inkscape:connector-curvature="0"
style="fill:#333333" /></g><path
d="m 121,150.8 c 2,-2 5.1,-2 6.8,0 2,2 2,5.1 0,6.8 -2,2 -5.1,2 -6.8,0 -1.8,-1.7 -1.8,-5 0,-6.8"
id="path41"
inkscape:connector-curvature="0"
style="fill:#333333" /><g
id="g43"><rect
x="85.099998"
y="113.2"
transform="matrix(-0.9064,-0.4224,0.4224,-0.9064,152.6722,266.0858)"
width="41.400002"
height="5.8000002"
id="rect45"
style="fill:#333333" /></g></g></g></g></g><g
id="Layer_2_copy"
display="none"
style="display:none"
transform="translate(0,-362)"><path
display="inline"
d="m 500,511.5 -488,0 c -6.6,0 -12,-5.4 -12,-12 l 0,-168 c 0,-6.6 5.4,-12 12,-12 l 488,0 c 6.6,0 12,5.4 12,12 l 0,168 c 0,6.6 -5.4,12 -12,12 z"
id="path48"
inkscape:connector-curvature="0"
style="display:inline;fill:#ffffff" /><g
id="Layer_2"
display="inline"
style="display:inline"><path
d="M 500,384 12,384 C 5.4,384 0,378.6 0,372 L 0,12 C 0,5.4 5.4,0 12,0 l 488,0 c 6.6,0 12,5.4 12,12 l 0,360 c 0,6.6 -5.4,12 -12,12 z"
id="path51"
inkscape:connector-curvature="0"
style="fill:#368db0" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<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 55.5 55.5" enable-background="new 0 0 55.5 55.5" xml:space="preserve">
<g>
<g>
<path fill="#0E7077" d="M47.4,8.1C42.1,2.9,35.2,0,27.8,0C20.3,0,13.4,2.9,8.1,8.1C2.9,13.4,0,20.3,0,27.8
c0,7.4,2.9,14.4,8.1,19.6c5.2,5.2,12.2,8.1,19.6,8.1c7.4,0,14.4-2.9,19.6-8.1c5.2-5.2,8.1-12.2,8.1-19.6
C55.5,20.3,52.6,13.4,47.4,8.1z M45.4,45.4c-4.7,4.7-11,7.3-17.7,7.3c-6.7,0-12.9-2.6-17.7-7.3c-4.7-4.7-7.3-11-7.3-17.7
c0-6.7,2.6-12.9,7.3-17.7c4.7-4.7,11-7.3,17.7-7.3c6.7,0,12.9,2.6,17.7,7.3c4.7,4.7,7.3,11,7.3,17.7
C52.7,34.4,50.1,40.7,45.4,45.4z"/>
<g>
<path fill="#0E7077" d="M20.3,37.6c-0.6,0-1.2-0.5-1.2-1.2V15c0-0.6,0.5-1.2,1.2-1.2c0.6,0,1.2,0.5,1.2,1.2v21.5
C21.5,37.1,21,37.6,20.3,37.6z"/>
<path fill="#0E7077" d="M21.7,14.4c-0.8,0.8-2,0.8-2.7,0c-0.8-0.8-0.8-2,0-2.7c0.8-0.8,2-0.8,2.7,0
C22.5,12.5,22.5,13.7,21.7,14.4"/>
<g>
<path fill="#0E7077" d="M20.3,16.2c-0.8,0-1.6-0.3-2.2-0.9c-1.2-1.2-1.2-3.2,0-4.4c0.6-0.6,1.4-0.9,2.2-0.9
c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C21.9,15.8,21.2,16.2,20.3,16.2z M20.3,12.3
c-0.2,0-0.4,0.1-0.6,0.2c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6
C20.7,12.4,20.5,12.3,20.3,12.3z"/>
</g>
<path fill="#0E7077" d="M19,37c0.8-0.8,2-0.8,2.7,0c0.8,0.8,0.8,2,0,2.7c-0.8,0.8-2,0.8-2.7,0C18.2,39,18.2,37.8,19,37"/>
<g>
<path fill="#0E7077" d="M20.3,41.5c-0.8,0-1.6-0.3-2.2-0.9c-0.6-0.6-0.9-1.4-0.9-2.2c0-0.8,0.3-1.6,0.9-2.2
c0.6-0.6,1.4-0.9,2.2-0.9c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C21.9,41.2,21.2,41.5,20.3,41.5z
M20.3,37.6c-0.2,0-0.4,0.1-0.6,0.2c-0.1,0.1-0.2,0.3-0.2,0.6c0,0.2,0.1,0.4,0.2,0.6c0.3,0.3,0.8,0.3,1.1,0
c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6C20.7,37.7,20.5,37.6,20.3,37.6z"/>
</g>
<g>
<path fill="#0E7077" d="M35.2,41.7c-0.6,0-1.2-0.5-1.2-1.2V19c0-0.6,0.5-1.2,1.2-1.2c0.6,0,1.2,0.5,1.2,1.2v21.5
C36.4,41.2,35.8,41.7,35.2,41.7z"/>
</g>
<path fill="#0E7077" d="M36.6,18.5c-0.8,0.8-2,0.8-2.7,0c-0.8-0.8-0.8-2,0-2.7c0.8-0.8,2-0.8,2.7,0
C37.3,16.5,37.3,17.7,36.6,18.5"/>
<g>
<path fill="#0E7077" d="M35.2,20.2c-0.8,0-1.6-0.3-2.2-0.9c-1.2-1.2-1.2-3.2,0-4.4c0.6-0.6,1.4-0.9,2.2-0.9
c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C36.8,19.9,36,20.2,35.2,20.2z M35.2,16.3
c-0.2,0-0.4,0.1-0.6,0.2c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6
C35.6,16.4,35.4,16.3,35.2,16.3z"/>
</g>
<g>
<path fill="#0E7077" d="M35.2,45.6c-0.8,0-1.6-0.3-2.2-0.9c-1.2-1.2-1.2-3.2,0-4.4c0.6-0.6,1.4-0.9,2.2-0.9
c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C36.8,45.2,36,45.6,35.2,45.6z M35.2,41.7
c-0.2,0-0.4,0.1-0.6,0.2c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.1-0.1,0.2-0.3,0.2-0.6s-0.1-0.4-0.2-0.6
C35.6,41.8,35.4,41.7,35.2,41.7z"/>
</g>
<path fill="#0E7077" d="M33.8,41.1c0.8-0.8,2-0.8,2.7,0c0.8,0.8,0.8,2,0,2.7c-0.8,0.8-2,0.8-2.7,0C33.1,43.1,33.1,41.8,33.8,41.1
"/>
<g>
<rect x="19.6" y="26.2" transform="matrix(0.9064 0.4224 -0.4224 0.9064 14.1626 -9.1647)" fill="#0E7077" width="16.4" height="2.3"/>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,157 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
viewBox="0 0 360 120.9"
data-name="Layer 1"
id="Layer_1">
<metadata
id="metadata5178">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Artboard 1</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs5143">
<style
id="style5141">.cls-1{fill:#fff;}</style>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="201.98439"
x2="188.125"
y1="74.484383"
x1="52.476562"
id="linearGradient7736"
xlink:href="#linearGradient7734"
gradientTransform="matrix(0.36497785,0,0,0.36497785,267.61793,-6.286912)" />
<linearGradient
id="linearGradient7734">
<stop
id="stop7730"
offset="0"
style="stop-color:#ff0000;stop-opacity:1" />
<stop
style="stop-color:#ffff00;stop-opacity:1"
offset="0.39864159"
id="stop7738" />
<stop
id="stop7740"
offset="0.54261029"
style="stop-color:#ccff00;stop-opacity:1" />
<stop
style="stop-color:#00ffff;stop-opacity:1"
offset="0.71236038"
id="stop7742" />
<stop
id="stop7744"
offset="0.99037486"
style="stop-color:#ff00ff;stop-opacity:1" />
<stop
id="stop7732"
offset="1"
style="stop-color:#0000ff;stop-opacity:1" />
</linearGradient>
<filter
height="1.2171938"
y="-0.10859691"
width="1.1880955"
x="-0.09404768"
id="filter7597"
style="color-interpolation-filters:sRGB">
<feGaussianBlur
id="feGaussianBlur7599"
stdDeviation="4.2634807" />
</filter>
</defs>
<title
id="title5145">Artboard 1</title>
<g
id="text4562"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
aria-label="Project Athena">
<path
id="path5741"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="M 0.3661321,106.82553 V 72.265531 H 17.406132 q 4.944,0 8.208,2.592 3.264,2.592 4.224,7.296 0.336,1.632 0.336,3.312 0,5.136 -3.12,8.304 -3.072,3.12 -7.92,3.12 h -6 v 9.935999 z M 15.198132,88.057531 q 2.592,0 3.024,-2.112 0.144,-0.72 0.144,-1.296 0,-1.872 -1.488,-2.496 -0.24,-0.096 -0.624,-0.144 -0.384,-0.048 -1.296,-0.048 h -1.824 v 6.096 z" />
<path
id="path5743"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="M 32.428632,106.82553 V 88.009531 l 11.04,-6.624 v 3.792 l 0.576,-0.528 q 2.832,-2.592 6.768,-2.592 0.528,0 0.912,0 0.384,0 1.008,0.144 0.624,0.144 1.056,0.24 0.48,0.096 1.296,0.432 l -3.696,11.136 q -0.288,-0.288 -0.816,-0.624 -0.528,-0.336 -1.104,-0.528 -0.528,-0.24 -1.344,-0.24 -1.488,0 -2.544,0.912 -1.008,0.864 -1.2,2.064 -0.192,1.152 -0.192,2.496 v 8.735999 z" />
<path
id="path5745"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 54.405132,94.825531 q 0,-5.424 3.888,-9.264 3.936,-3.888 9.504,-3.888 2.736,0 5.184,1.056 3.84,1.584 6,4.944 2.16,3.36 2.16,7.248 0,2.496 -0.96,4.848 -0.912,2.351999 -2.832,4.271999 -3.936,3.936 -9.552,3.936 -5.616,0 -9.504,-3.84 -3.888,-3.84 -3.888,-9.311999 z m 11.712,2.544 q 0.672,0.72 1.68,0.72 1.008,0 1.728,-0.816 0.768,-0.864 0.768,-2.448 0,-1.536 -0.768,-2.4 -0.72,-0.864 -1.728,-0.864 -1.008,0 -1.68,0.768 -0.864,0.96 -0.864,2.496 0,1.584 0.864,2.544 z" />
<path
id="path5747"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="M 84.272382,115.46553 V 87.913531 l 11.76,-6.48 v 34.031999 z m 0.096,-39.167999 q 0,-2.592 1.68,-4.512 1.68,-1.92 4.224,-1.92 1.392,0 2.544,0.432 1.152,0.384 2.064,1.296 1.824,1.776 1.824,4.368 0,2.544 -1.776,4.368 -1.728,1.776 -4.176,1.776 -2.592,0 -4.512,-1.632 -1.872,-1.68 -1.872,-4.176 z" />
<path
id="path5749"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 124.75588,91.465531 -13.44,6.816 0.24,0.336 q 0.768,0.768 1.584,0.768 0.336,0 0.816,-0.192 0.48,-0.192 0.912,-0.672 l 0.096,-0.192 0.144,-0.192 7.488,4.511999 q -1.44,1.92 -3.552,3.216 -2.112,1.248 -4.656,1.728 -1.344,0.24 -2.496,0.24 -3.504,0 -6.48,-1.776 -6.335998,-3.6 -6.335998,-10.799999 0,-1.104 0.24,-2.544 0.624,-4.08 3.455998,-6.96 2.832,-2.88 6.912,-3.648 1.248,-0.192 1.728,-0.192 0.528,-0.048 0.816,-0.048 1.296,0 2.544,0.288 6.864,1.488 9.552,7.968 z m -11.52,-1.728 q -0.96,0 -1.392,0.144 -1.44,0.432 -2.112,1.488 -0.672,1.056 -0.672,2.112 0,0.384 0.192,1.056 l 6.624,-3.36 q -0.48,-0.624 -1.152,-1.008 -0.672,-0.432 -1.488,-0.432 z" />
<path
id="path5751"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 148.01638,83.785531 -2.592,8.64 -0.432,-0.288 q -1.344,-0.816 -2.208,-0.96 -0.864,-0.192 -1.536,-0.192 -1.68,0 -2.688,1.488 -0.576,1.008 -0.576,2.064 0,0.288 0.096,0.768 0.624,2.736 3.456,2.736 1.392,0 3.024,-0.672 l 0.576,-0.192 0.288,-0.144 2.4,7.919999 q -0.912,0.672 -2.16,1.2 -3.6,1.632 -7.008,1.632 -6.72,0 -10.56,-5.856 -2.16,-3.215999 -2.16,-7.199999 0,-1.248 0.288,-2.64 0.96,-4.704 4.56,-7.536 3.648,-2.88 8.688,-2.88 3.264,0 6.528,1.248 0.576,0.192 0.96,0.384 0.432,0.192 1.056,0.48 z" />
<path
id="path5753"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 147.34438,91.609531 16.128,-20.208 v 11.232 h 5.616 l -3.12,8.976 h -2.496 v 15.215999 h -11.76 V 91.609531 Z" />
<path
id="path5755"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 179.49613,106.82553 12.288,-34.559999 h 13.296 l 12.288,34.559999 h -13.824 l -0.96,-4.416 h -8.304 l -1.008,4.416 z m 21.6,-13.151999 -1.872,-8.256 -0.432,-1.968 -0.288,-2.304 q 0,0.912 -0.144,1.392 -0.096,0.48 -0.192,0.96 l -0.48,1.92 -1.872,8.256 z" />
<path
id="path5757"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 214.75063,91.609531 16.128,-20.208 v 11.232 h 5.616 l -3.12,8.976 h -2.496 v 15.215999 h -11.76 V 91.609531 Z" />
<path
id="path5759"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="M 236.14738,106.82553 V 77.689531 l 11.76,-6.528 v 13.488 l 0.288,-0.336 0.384,-0.336 q 1.152,-1.008 2.592,-1.536 2.016,-0.72 3.888,-0.72 3.6,0 5.904,2.4 2.304,2.4 2.304,6.144 v 16.559999 h -11.76 V 94.393531 q 0,-2.688 -2.112,-2.4 -1.488,0.288 -1.488,2.448 v 12.383999 z" />
<path
id="path5761"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 292.00588,91.465531 -13.44,6.816 0.24,0.336 q 0.768,0.768 1.584,0.768 0.336,0 0.816,-0.192 0.48,-0.192 0.912,-0.672 l 0.096,-0.192 0.144,-0.192 7.488,4.511999 q -1.44,1.92 -3.552,3.216 -2.112,1.248 -4.656,1.728 -1.344,0.24 -2.496,0.24 -3.504,0 -6.48,-1.776 -6.336,-3.6 -6.336,-10.799999 0,-1.104 0.24,-2.544 0.624,-4.08 3.456,-6.96 2.832,-2.88 6.912,-3.648 1.248,-0.192 1.728,-0.192 0.528,-0.048 0.816,-0.048 1.296,0 2.544,0.288 6.864,1.488 9.552,7.968 z m -11.52,-1.728 q -0.96,0 -1.392,0.144 -1.44,0.432 -2.112,1.488 -0.672,1.056 -0.672,2.112 0,0.384 0.192,1.056 l 6.624,-3.36 q -0.48,-0.624 -1.152,-1.008 -0.672,-0.432 -1.488,-0.432 z" />
<path
id="path5763"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 293.52238,87.913531 11.04,-6.576 v 3.552 q 1.152,-1.44 3.024,-2.208 1.872,-0.816 4.464,-0.816 4.128,0 6.336,2.352 2.256,2.304 2.256,6.528 v 16.079999 h -11.76 V 94.153531 q 0,-2.304 -1.776,-2.304 -1.776,0 -1.776,2.88 l 0.048,12.095999 h -11.76 z" />
<path
id="path5765"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48px;font-family:OpineHeavy;-inkscape-font-specification:OpineHeavy;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
d="m 341.70088,84.889531 v -2.256 h 11.088 v 24.191999 h -11.088 v -2.496 q -1.2,1.488 -3.072,2.304 -1.872,0.816 -4.32,0.816 -4.56,0 -7.44,-3.12 -3.12,-3.36 -3.12,-9.215999 0,-5.856 3.072,-9.504 3.072,-3.648 7.584,-3.648 1.008,0 2.256,0.24 1.248,0.24 2.64,0.96 1.392,0.72 2.4,1.728 z m -3.84,12.864 q 0.192,0.048 0.336,0.048 0.144,0 0.192,0 1.104,0 1.824,-0.912 0.768,-0.96 0.768,-2.64 0,-1.344 -0.576,-2.112 -0.528,-0.816 -1.536,-1.056 -0.192,-0.048 -0.528,-0.048 -2.256,0 -2.64,2.88 v 0.624 q 0,1.392 0.528,2.256 0.576,0.816 1.632,0.96 z" />
</g>
<path
transform="matrix(0.69899025,0,0,0.69899025,269.31594,32.684837)"
id="path3799-2"
d="m 91.832079,57.500977 -54.399819,-3e-6 -27.199907,-47.111626 27.199912,-47.111623 54.399818,3e-6 27.199907,47.111626 z"
style="display:inline;opacity:0.93500001;fill:#3a3937;fill-opacity:1;stroke:#0092d4;stroke-width:9.56400013;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter7597)" />
<path
id="path4704"
d="m 333.58541,10.641061 c -1.33042,-0.003 -2.6614,0.02719 -3.99105,0.08885 -10.63729,0.492794 -21.18542,3.054485 -30.61336,7.960541 -12.58659,6.549722 -22.9644,17.350793 -27.86872,30.474604 -0.74714,1.999314 -1.43163,4.195201 -1.50307,6.589093 -0.0565,1.888571 0.29775,3.884417 1.27773,5.657351 l 3.81151,-1.936715 c -0.56198,-1.016544 -0.833,-2.297505 -0.79411,-3.599793 0.0501,-1.679736 0.56345,-3.455978 1.24801,-5.287791 4.49756,-12.035358 14.14243,-22.123913 25.85832,-28.220559 8.79888,-4.578713 18.73478,-7.001788 28.79335,-7.470115 3.35287,-0.156099 6.7192,-0.09515 10.06201,0.175426 2.93218,0.237351 5.73367,0.646171 8.1239,1.68684 1.19179,0.518899 2.27932,1.195515 3.16519,2.017423 0.42257,0.392059 0.80015,0.822607 1.12389,1.278292 l 3.82437,-1.942984 c -0.5612,-0.867683 -1.23086,-1.656127 -1.97867,-2.349944 -1.30077,-1.206889 -2.80875,-2.124025 -4.37706,-2.806858 -3.12999,-1.362756 -6.42744,-1.788546 -9.52453,-2.039252 -2.20449,-0.178433 -4.42036,-0.270573 -6.63771,-0.274441 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4.23338938;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
id="rect4616"
d="M 332.27108,6.4873186 302.92243,35.836669 V 7.463203 h -8.85213 v 64.708723 h 8.85213 v -23.81623 l 5.76336,-5.764087 25.34887,25.348857 6.25951,-6.259517 -25.34886,-25.348857 23.58527,-23.585265 z"
style="display:inline;opacity:1;fill:url(#linearGradient7736);fill-opacity:1;stroke:none;stroke-width:7.73268747;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="display:inline;fill:none;fill-opacity:1;stroke:#0092d4;stroke-width:6.68530035;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 333.50566,72.877373 -38.02494,-2e-6 -19.01247,-32.930565 19.01248,-32.9305635 38.02494,1.8e-6 19.01247,32.9305657 z"
id="path3799" />
<path
id="path4704-6"
d="m 356.08812,18.088179 -3.82438,1.945692 c 0.35892,0.501671 0.65258,1.033693 0.86041,1.582768 0.50474,1.333501 0.56989,2.917749 0.29001,4.495637 -0.28192,1.589386 -0.90095,3.203676 -1.66094,4.844551 -5.48664,11.845931 -16.20091,21.205609 -28.57285,26.516947 -12.39358,5.320605 -26.41056,6.815809 -40.05475,5.569639 -1.68347,-0.153759 -3.24995,-0.35005 -4.62129,-0.787094 -1.35534,-0.431958 -2.54518,-1.130952 -3.29455,-2.02716 -0.18643,-0.22298 -0.36768,-0.495132 -0.51492,-0.758591 l -3.81239,1.922314 c 0.2748,0.493806 0.61298,1.012955 0.9906,1.464582 1.42784,1.70764 3.3631,2.74692 5.29048,3.361179 1.9114,0.609193 3.80307,0.816132 5.5596,0.976564 14.23496,1.300129 28.98,-0.233493 42.19527,-5.906858 13.23694,-5.68266 24.75883,-15.678884 30.75317,-28.620923 0.8299,-1.791855 1.60226,-3.728332 1.97824,-5.847981 0.37802,-2.131162 0.34038,-4.441307 -0.49205,-6.640562 -0.28066,-0.741451 -0.64526,-1.437996 -1.06966,-2.090704 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4.23338938;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!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" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="1880" height="320" viewBox="0 0 1880.00 320.00" enable-background="new 0 0 1880.00 320.00" xml:space="preserve">
<radialGradient id="SVGID_Fill1_" cx="-571.529" cy="-604.157" r="3176.39" gradientUnits="userSpaceOnUse">
<stop offset="0.451163" stop-color="#01BDFF" stop-opacity="1"/>
<stop offset="0.827907" stop-color="#8C1AFF" stop-opacity="1"/>
</radialGradient>
<path fill="url(#SVGID_Fill1_)" stroke-width="0.2" stroke-linejoin="round" d="M 28.723,5.00146L 1706.72,5.00146C 1726.61,5.00146 1750.24,21.1192 1759.51,41.0015L 1870.49,278.999C 1879.76,298.881 1871.16,314.999 1851.28,314.999L 173.277,314.999C 153.395,314.999 129.761,298.881 120.49,278.999L 9.51012,41.0015C 0.238872,21.1192 8.84079,5.00146 28.723,5.00146 Z "/>
<path fill="#36393F" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 678.939,23.013L 1697.3,23.0124C 1723.4,22.9324 1739.32,35.2514 1749.85,58.4911L 1844.51,261.508C 1860.58,289.515 1859.52,298.385 1825.06,296.987L 677.061,296.987"/>
<g>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 699.7,159.966C 699.7,145.281 702.032,131.093 706.698,117.404C 711.363,103.714 718.063,91.58 726.796,81.0016C 735.529,70.4231 745.996,61.9604 758.199,55.6133C 770.401,49.2663 784.039,46.0927 799.112,46.0927L 906.779,46.0927L 906.779,93.8823L 800.189,93.8823C 794.686,93.8823 789.003,95.7491 783.142,99.4827C 777.28,103.216 771.896,108.132 766.992,114.23C 762.087,120.328 758.019,127.36 754.789,135.325C 751.559,143.29 749.944,151.379 749.944,159.593C 749.944,167.807 751.499,175.896 754.61,183.861C 757.72,191.826 761.788,198.92 766.812,205.142C 771.837,211.365 777.459,216.343 783.68,220.077C 789.901,223.81 796.122,225.677 802.342,225.677L 906.779,225.677L 906.779,273.467L 798.036,273.467C 782.244,273.467 768.248,270.044 756.045,263.199C 743.843,256.354 733.555,247.456 725.181,236.504C 716.807,225.553 710.466,213.356 706.16,199.915C 701.853,186.475 699.7,173.158 699.7,159.966 Z "/>
<rect x="1464.81" y="46.0927" fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" width="55.9871" height="227.374"/>
</g>
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 493.02,97.6159L 493.02,139.602L 493,139.602L 493,199.602L 493.02,199.602L 493.02,273.467L 439.123,273.467L 439.123,46.0928L 566.737,46.0928C 578.327,46.0928 588.237,48.2707 596.467,52.6265C 604.696,56.9823 611.419,62.7071 616.635,69.8009C 621.851,76.8946 625.618,84.9218 627.936,93.8824C 630.254,102.843 631.413,111.928 631.413,121.137C 631.413,128.604 630.428,136.009 628.457,143.352C 626.487,150.695 623.763,157.54 620.286,163.887C 616.809,170.234 612.578,175.896 607.594,180.874C 602.61,185.852 597.104,189.71 591.077,192.448L 654.363,273.467L 588.295,273.467L 521,188.288L 521,149.512L 566.737,149.512C 568.359,149.512 569.982,148.517 571.605,146.526C 573.227,144.534 574.618,142.17 575.777,139.432C 576.937,136.694 577.922,133.894 578.733,131.031C 579.544,128.169 579.95,125.991 579.95,124.498C 579.95,122.257 579.718,119.644 579.255,116.657C 578.791,113.67 578.038,110.746 576.995,107.883C 575.951,105.021 574.56,102.594 572.822,100.603C 571.083,98.6115 569.055,97.6159 566.737,97.6159L 493.02,97.6159 Z "/>
<g>
<rect x="340.465" y="46.0927" fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" width="54.9846" height="227.374"/>
</g>
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 55.5166,46.093L 161.961,273.467L 214.479,273.467L 216.626,269.689L 115.436,46.093L 55.5166,46.093 Z "/>
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 231.92,235.709L 319.513,46.0926L 259.594,46.0926L 202.957,171.669L 231.92,235.709 Z "/>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 987.874,273.467L 926.863,273.467L 1034.17,46.0927L 1088,46.0927L 1195.67,273.467L 1134.66,273.467L 1106.67,212.983L 1047.45,212.983L 1044.83,212.918L 1065.94,164.447L 1087.65,164.447L 1060.73,106.576"/>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 1605.13,273.467L 1544.12,273.467L 1651.43,46.0927L 1705.26,46.0927L 1812.93,273.467L 1751.91,273.467L 1723.92,212.983L 1664.7,212.983L 1662,212.918L 1683.24,164.447L 1704.9,164.447L 1677.98,106.576"/>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 1438.98,159.966C 1438.98,173.158 1436.83,186.475 1432.52,199.915C 1428.22,213.356 1421.88,225.553 1413.5,236.504C 1405.13,247.456 1394.84,256.354 1382.64,263.199C 1370.43,270.044 1356.44,273.467 1340.65,273.467L 1217.91,273.467L 1217.91,121.9L 1272.1,121.9L 1272.1,225.677L 1335.98,225.677C 1342.2,225.677 1348.42,223.81 1354.64,220.077C 1360.86,216.343 1366.49,211.365 1371.51,205.142C 1376.54,198.92 1380.6,191.826 1383.71,183.861C 1386.82,175.896 1388.38,167.807 1388.38,159.593C 1388.38,150.632 1386.58,142.17 1383,134.205C 1379.41,126.24 1375.04,119.271 1369.9,113.297C 1364.75,107.323 1359.31,102.594 1353.57,99.1094C 1347.82,95.6247 1342.8,93.8824 1338.49,93.8824L 1272.1,93.8824L 1272.1,93.9001L 1217.91,93.9001L 1217.91,46.0927L 1332.03,46.0927C 1350.46,46.0927 1366.43,49.0796 1379.95,55.0533C 1393.46,61.0269 1404.59,69.1163 1413.32,79.3215C 1422.06,89.5265 1428.52,101.536 1432.7,115.35C 1436.89,129.165 1438.98,144.037 1438.98,159.966 Z "/>
</svg>

View file

@ -1,23 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!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" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="1880" height="320" viewBox="0 0 1880.00 320.00" enable-background="new 0 0 1880.00 320.00" xml:space="preserve">
<radialGradient id="SVGID_Fill1_" cx="-571.529" cy="-604.157" r="3176.39" gradientUnits="userSpaceOnUse">
<stop offset="0.451163" stop-color="#01BDFF" stop-opacity="1"/>
<stop offset="0.827907" stop-color="#8C1AFF" stop-opacity="1"/>
</radialGradient>
<path fill="url(#SVGID_Fill1_)" stroke-width="0.2" stroke-linejoin="round" d="M 28.723,5.00146L 1706.72,5.00146C 1726.61,5.00146 1750.24,21.1192 1759.51,41.0015L 1870.49,278.999C 1879.76,298.881 1871.16,314.999 1851.28,314.999L 173.277,314.999C 153.395,314.999 129.761,298.881 120.49,278.999L 9.51012,41.0015C 0.238872,21.1192 8.84079,5.00146 28.723,5.00146 Z "/>
<path fill="#36393F" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 678.939,23.013L 1697.3,23.0124C 1723.4,22.9324 1739.32,35.2514 1749.85,58.4911L 1844.51,261.508C 1860.58,289.515 1859.52,298.385 1825.06,296.987L 677.061,296.987"/>
<g>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 699.7,159.966C 699.7,145.281 702.032,131.093 706.698,117.404C 711.363,103.714 718.063,91.58 726.796,81.0016C 735.529,70.4231 745.996,61.9604 758.199,55.6133C 770.401,49.2663 784.039,46.0927 799.112,46.0927L 906.779,46.0927L 906.779,93.8823L 800.189,93.8823C 794.686,93.8823 789.003,95.7491 783.142,99.4827C 777.28,103.216 771.896,108.132 766.992,114.23C 762.087,120.328 758.019,127.36 754.789,135.325C 751.559,143.29 749.944,151.379 749.944,159.593C 749.944,167.807 751.499,175.896 754.61,183.861C 757.72,191.826 761.788,198.92 766.812,205.142C 771.837,211.365 777.459,216.343 783.68,220.077C 789.901,223.81 796.122,225.677 802.342,225.677L 906.779,225.677L 906.779,273.467L 798.036,273.467C 782.244,273.467 768.248,270.044 756.045,263.199C 743.843,256.354 733.555,247.456 725.181,236.504C 716.807,225.553 710.466,213.356 706.16,199.915C 701.853,186.475 699.7,173.158 699.7,159.966 Z "/>
<rect x="1464.81" y="46.0927" fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" width="55.9871" height="227.374"/>
</g>
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 493.02,97.6159L 493.02,139.602L 493,139.602L 493,199.602L 493.02,199.602L 493.02,273.467L 439.123,273.467L 439.123,46.0928L 566.737,46.0928C 578.327,46.0928 588.237,48.2707 596.467,52.6265C 604.696,56.9823 611.419,62.7071 616.635,69.8009C 621.851,76.8946 625.618,84.9218 627.936,93.8824C 630.254,102.843 631.413,111.928 631.413,121.137C 631.413,128.604 630.428,136.009 628.457,143.352C 626.487,150.695 623.763,157.54 620.286,163.887C 616.809,170.234 612.578,175.896 607.594,180.874C 602.61,185.852 597.104,189.71 591.077,192.448L 654.363,273.467L 588.295,273.467L 521,188.288L 521,149.512L 566.737,149.512C 568.359,149.512 569.982,148.517 571.605,146.526C 573.227,144.534 574.618,142.17 575.777,139.432C 576.937,136.694 577.922,133.894 578.733,131.031C 579.544,128.169 579.95,125.991 579.95,124.498C 579.95,122.257 579.718,119.644 579.255,116.657C 578.791,113.67 578.038,110.746 576.995,107.883C 575.951,105.021 574.56,102.594 572.822,100.603C 571.083,98.6115 569.055,97.6159 566.737,97.6159L 493.02,97.6159 Z "/>
<g>
<rect x="340.465" y="46.0927" fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" width="54.9846" height="227.374"/>
</g>
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 55.5166,46.093L 161.961,273.467L 214.479,273.467L 216.626,269.689L 115.436,46.093L 55.5166,46.093 Z "/>
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 231.92,235.709L 319.513,46.0926L 259.594,46.0926L 202.957,171.669L 231.92,235.709 Z "/>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 987.874,273.467L 926.863,273.467L 1034.17,46.0927L 1088,46.0927L 1195.67,273.467L 1134.66,273.467L 1106.67,212.983L 1047.45,212.983L 1044.83,212.918L 1065.94,164.447L 1087.65,164.447L 1060.73,106.576"/>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 1605.13,273.467L 1544.12,273.467L 1651.43,46.0927L 1705.26,46.0927L 1812.93,273.467L 1751.91,273.467L 1723.92,212.983L 1664.7,212.983L 1662,212.918L 1683.24,164.447L 1704.9,164.447L 1677.98,106.576"/>
<path fill="#FAFAFA" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 1438.98,159.966C 1438.98,173.158 1436.83,186.475 1432.52,199.915C 1428.22,213.356 1421.88,225.553 1413.5,236.504C 1405.13,247.456 1394.84,256.354 1382.64,263.199C 1370.43,270.044 1356.44,273.467 1340.65,273.467L 1217.91,273.467L 1217.91,121.9L 1272.1,121.9L 1272.1,225.677L 1335.98,225.677C 1342.2,225.677 1348.42,223.81 1354.64,220.077C 1360.86,216.343 1366.49,211.365 1371.51,205.142C 1376.54,198.92 1380.6,191.826 1383.71,183.861C 1386.82,175.896 1388.38,167.807 1388.38,159.593C 1388.38,150.632 1386.58,142.17 1383,134.205C 1379.41,126.24 1375.04,119.271 1369.9,113.297C 1364.75,107.323 1359.31,102.594 1353.57,99.1094C 1347.82,95.6247 1342.8,93.8824 1338.49,93.8824L 1272.1,93.8824L 1272.1,93.9001L 1217.91,93.9001L 1217.91,46.0927L 1332.03,46.0927C 1350.46,46.0927 1366.43,49.0796 1379.95,55.0533C 1393.46,61.0269 1404.59,69.1163 1413.32,79.3215C 1422.06,89.5265 1428.52,101.536 1432.7,115.35C 1436.89,129.165 1438.98,144.037 1438.98,159.966 Z "/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg17"
xml:space="preserve"
enable-background="new 0 0 256.00 256.00"
viewBox="0 0 257.6 257.6"
height="56"
width="56"
version="1.1"><metadata
id="metadata23"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs21" />
<radialGradient
gradientTransform="translate(1.15,1.25)"
gradientUnits="userSpaceOnUse"
r="410"
cy="-40.865398"
cx="-40.865398"
id="SVGID_Fill1_">
<stop
id="stop2"
stop-opacity="1"
stop-color="#01BDFF"
offset="0.35814" />
<stop
id="stop4"
stop-opacity="1"
stop-color="#466BFF"
offset="0.618605" />
<stop
id="stop6"
stop-opacity="1"
stop-color="#8C1AFF"
offset="0.855814" />
</radialGradient>
<circle
style="fill:url(#SVGID_Fill1_);stroke-width:0.2;stroke-linejoin:round"
r="125"
id="ellipse9"
cy="129.25"
cx="129.14999" />
<g
transform="translate(1.15,1.25)"
id="g15">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.2;stroke-linejoin:round"
id="path11"
d="m 48,65.0981 64.513,137.8039 h 31.829 l 1.301,-2.289 L 84.3151,65.0981 Z" />
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:0.2;stroke-linejoin:round"
id="path13"
d="M 154.913,180.019 208,65.0978 h -36.315 l -34.326,76.1082 z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -3,10 +3,10 @@
//
// Created by David Rowe on 3 Jun 2015
// Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
//
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
@ -87,7 +87,7 @@ FocusScope {
anchors.centerIn: parent
sourceSize.width: 500
sourceSize.height: 91
source: "../images/vircadia-logo.svg"
source: "../images/vircadia-banner.svg"
horizontalAlignment: Image.AlignHCenter
}
}

View file

@ -3,6 +3,7 @@
//
// Created by Clement on 7/18/16
// Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -486,6 +487,7 @@ Item {
}
}
}
Item {
id: signUpContainer
width: loginContainer.width
@ -494,7 +496,7 @@ Item {
anchors {
left: loginContainer.left
top: loginContainer.bottom
topMargin: 0.15 * parent.height
topMargin: 0.05 * parent.height
}
TextMetrics {
id: signUpTextMetrics
@ -542,37 +544,54 @@ Item {
"errorString": "" });
}
}
}
TextMetrics {
id: dismissButtonTextMetrics
font: loginErrorMessage.font
text: dismissButton.text
}
HifiControlsUit.Button {
id: dismissButton
width: dismissButtonTextMetrics.width
height: d.minHeightButton
anchors {
bottom: parent.bottom
right: parent.right
margins: 3 * hifi.dimensions.contentSpacing.y
}
color: hifi.buttons.noneBorderlessWhite
text: qsTr("No thanks, take me in-world! >")
fontCapitalization: Font.MixedCase
fontFamily: linkAccountBody.fontFamily
fontSize: linkAccountBody.fontSize
fontBold: linkAccountBody.fontBold
visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
onClicked: {
if (linkAccountBody.loginDialogPoppedUp) {
var data = {
"action": "user dismissed login screen"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
loginDialog.dismissLoginDialog();
Text {
id: signUpTextSecond
text: qsTr("or")
anchors {
left: signUpShortcutText.right
leftMargin: hifi.dimensions.contentSpacing.x
}
lineHeight: 1
color: "white"
font.family: linkAccountBody.fontFamily
font.pixelSize: linkAccountBody.textFieldFontSize
font.bold: linkAccountBody.fontBold
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
}
TextMetrics {
id: dismissButtonTextMetrics
font: loginErrorMessage.font
text: dismissButton.text
}
HifiControlsUit.Button {
id: dismissButton
width: loginButton.width
height: d.minHeightButton
anchors {
top: signUpText.bottom
topMargin: hifi.dimensions.contentSpacing.y
left: loginButton.left
}
text: qsTr("Use without account, log in anonymously")
fontCapitalization: Font.MixedCase
fontFamily: linkAccountBody.fontFamily
fontSize: linkAccountBody.fontSize
fontBold: linkAccountBody.fontBold
visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
onClicked: {
if (linkAccountBody.loginDialogPoppedUp) {
var data = {
"action": "user dismissed login screen"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
loginDialog.dismissLoginDialog();
}
root.tryDestroy();
}
root.tryDestroy();
}
}
}

View file

@ -83,7 +83,7 @@ FocusScope {
anchors.centerIn: parent
sourceSize.width: 500
sourceSize.height: 91
source: "../images/vircadia-logo.svg"
source: "../images/vircadia-banner.svg"
horizontalAlignment: Image.AlignHCenter
}
}

View file

@ -47,7 +47,7 @@ ScrollingWindow {
Image {
id: logo
source: "../images/hifi-logo.svg"
source: "../images/vircadia-logo.svg"
width: updateDialog.logoSize
height: updateDialog.logoSize
anchors {

View file

@ -131,7 +131,7 @@ FocusScope {
anchors.centerIn: parent
sourceSize.width: 400
sourceSize.height: 73
source: "../../images/vircadia-logo.svg"
source: "../../images/vircadia-banner.svg"
horizontalAlignment: Image.AlignHCenter
}
}

View file

@ -128,7 +128,7 @@ ShadowRectangle {
}
}
// FIXME: Link to a Project Athena version of the video.
// FIXME: Link to a Vircadias version of the video.
/*
RalewayButton {
id: video

View file

@ -25,7 +25,7 @@ Rectangle {
Image {
width: 400; height: 73
fillMode: Image.PreserveAspectFit
source: "../../../images/vircadia-logo.svg"
source: "../../../images/vircadia-banner.svg"
}
Item { height: 30; width: 1 }
Column {
@ -53,7 +53,7 @@ Rectangle {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "<a href=\"https:/github.com/kasenvr/project-athena\">Vircadia Github</a>."
text: "<a href=\"https://github.com/kasenvr/project-athena\">Vircadia Github</a>."
size: 20
onLinkActivated: {
HiFiAbout.openUrl("https:/github.com/kasenvr/project-athena");

View file

@ -347,7 +347,6 @@ static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action";
static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)";
static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json";
static const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com";
static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds
static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
@ -7620,7 +7619,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
QUrl scriptURL { scriptFilenameOrURL };
if (scriptURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) {
if (scriptURL.host().endsWith(NetworkingConstants::MARKETPLACE_CDN_HOSTNAME)) {
int startIndex = shortName.lastIndexOf('/') + 1;
int endIndex = shortName.lastIndexOf('?');
shortName = shortName.mid(startIndex, endIndex - startIndex);
@ -7743,7 +7742,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
const int MAX_CHARACTERS_PER_LINE = 90;
if (DependencyManager::get<NodeList>()->getThisNodeCanReplaceContent()) {
QUrl originURL { url };
if (originURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) {
if (originURL.host().endsWith(NetworkingConstants::MARKETPLACE_CDN_HOSTNAME)) {
// Create a confirmation dialog when this call is made
static const QString infoText = simpleWordWrap("Your domain's content will be replaced with a new content set. "
"If you want to save what you have now, create a backup before proceeding. For more information about backing up "

View file

@ -223,9 +223,7 @@ public:
bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); }
void setPreferStylusOverLaser(bool value);
// FIXME: Remove setting completely or make available through JavaScript API?
//bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
bool getPreferAvatarFingerOverStylus() { return false; }
bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
void setPreferAvatarFingerOverStylus(bool value);
bool getMiniTabletEnabled() { return _miniTabletEnabledSetting.get(); }

View file

@ -799,19 +799,19 @@ Menu::Menu() {
// Help > Vircadia Docs
action = addActionToQMenuAndActionHash(helpMenu, "Online Documentation");
connect(action, &QAction::triggered, qApp, [] {
QDesktopServices::openUrl(QUrl("https://docs.vircadia.dev/"));
QDesktopServices::openUrl(NetworkingConstants::HELP_DOCS_URL);
});
// Help > Vircadia Forum
/* action = addActionToQMenuAndActionHash(helpMenu, "Online Forums");
connect(action, &QAction::triggered, qApp, [] {
QDesktopServices::openUrl(QUrl("https://forums.highfidelity.com/"));
QDesktopServices::openUrl(NetworkingConstants::HELP_FORUM_URL));
}); */
// Help > Scripting Reference
action = addActionToQMenuAndActionHash(helpMenu, "Online Script Reference");
connect(action, &QAction::triggered, qApp, [] {
QDesktopServices::openUrl(QUrl("https://apidocs.vircadia.dev/"));
QDesktopServices::openUrl(NetworkingConstants::HELP_SCRIPTING_REFERENCE_URL);
});
addActionToQMenuAndActionHash(helpMenu, "Controls Reference", 0, qApp, SLOT(showHelp()));
@ -821,13 +821,13 @@ Menu::Menu() {
// Help > Release Notes
action = addActionToQMenuAndActionHash(helpMenu, "Release Notes");
connect(action, &QAction::triggered, qApp, [] {
QDesktopServices::openUrl(QUrl("https://docs.vircadia.dev/release-notes.html"));
QDesktopServices::openUrl(NetworkingConstants::HELP_RELEASE_NOTES_URL);
});
// Help > Report a Bug!
action = addActionToQMenuAndActionHash(helpMenu, "Report a Bug!");
connect(action, &QAction::triggered, qApp, [] {
QDesktopServices::openUrl(QUrl("https://github.com/kasenvr/project-athena/issues"));
QDesktopServices::openUrl(NetworkingConstants::HELP_BUG_REPORT_URL);
});
}

View file

@ -95,7 +95,7 @@ public:
static bool isValidNewProjectName(const QString& projectPath, const QString& projectName);
static QString getDefaultProjectsPath() {
return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Project Athena Projects";
return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Vircadia Projects";
}
signals:

View file

@ -379,7 +379,7 @@ int main(int argc, const char* argv[]) {
PROFILE_SYNC_END(startup, "app full ctor", "");
#if defined(Q_OS_LINUX)
app.setWindowIcon(QIcon(PathUtils::resourcesPath() + "images/hifi-logo.svg"));
app.setWindowIcon(QIcon(PathUtils::resourcesPath() + "images/vircadia-logo.svg"));
#endif
QTimer exitTimer;

View file

@ -223,13 +223,11 @@ void setupPreferences() {
preferences->addPreference(preference);
}
/*
// FIXME: Remove setting completely or make available through JavaScript API?
{
auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
}*/
}
// Snapshots
static const QString SNAPSHOTS { "Snapshots" };

View file

@ -610,6 +610,14 @@ QString defaultAudioDeviceName(QAudio::Mode mode) {
<< " [" << deviceName << "] [" << "]";
#endif
#endif
#ifdef Q_OS_LINUX
if ( mode == QAudio::AudioInput ) {
deviceName = QAudioDeviceInfo::defaultInputDevice().deviceName();
} else {
deviceName = QAudioDeviceInfo::defaultOutputDevice().deviceName();
}
#endif
return deviceName;
}

View file

@ -16,6 +16,7 @@
#include <ApplicationVersion.h>
#include <BuildInfo.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <SharedUtil.h>
AutoUpdater::AutoUpdater() :
@ -36,18 +37,15 @@ void AutoUpdater::checkForUpdate() {
this->getLatestVersionData();
}
const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml");
const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml");
void AutoUpdater::getLatestVersionData() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QUrl buildsURL;
if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable) {
buildsURL = BUILDS_XML_URL;
buildsURL = NetworkingConstants::BUILDS_XML_URL;
} else if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master) {
buildsURL = MASTER_BUILDS_XML_URL;
buildsURL = NetworkingConstants::MASTER_BUILDS_XML_URL;
}
QNetworkRequest latestVersionRequest(buildsURL);

View file

@ -312,7 +312,10 @@ void UserInputMapper::update(float deltaTime) {
Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const {
Locker locker(_lock);
auto iterator = _registeredDevices.find(deviceID);
return iterator->second->getAvailableInputs();
if (iterator != _registeredDevices.end()) {
return iterator->second->getAvailableInputs();
}
return Input::NamedVector();
}
QVector<Action> UserInputMapper::getAllActions() const {
@ -366,7 +369,7 @@ bool UserInputMapper::triggerHapticPulse(float strength, float duration, control
Locker locker(_lock);
bool toReturn = false;
for (const auto& device : _registeredDevices) {
toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand);
toReturn = device.second->triggerHapticPulse(strength, duration, hand) || toReturn;
}
return toReturn;
}
@ -1237,16 +1240,42 @@ void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
}
void UserInputMapper::setActionState(Action action, float value, bool valid) {
Locker locker(_lock);
_actionStates[toInt(action)] = value;
_actionStatesValid[toInt(action)] = valid;
}
void UserInputMapper::deltaActionState(Action action, float delta, bool valid) {
Locker locker(_lock);
_actionStates[toInt(action)] += delta;
bool wasValid = _actionStatesValid[toInt(action)];
_actionStatesValid[toInt(action)] = wasValid & valid;
}
float UserInputMapper::getActionState(Action action) const {
Locker locker(_lock);
int index = toInt(action);
if (index >= 0 && index < _actionStates.size()) {
return _actionStates[index];
}
qCDebug(controllers) << "UserInputMapper::getActionState invalid action:" << index;
return 0.0f;
}
bool UserInputMapper::getActionStateValid(Action action) const {
Locker locker(_lock);
int index = toInt(action);
if (index >= 0 && index < _actionStatesValid.size()) {
return _actionStatesValid[index];
}
qCDebug(controllers) << "UserInputMapper::getActionStateValid invalid action:" << index;
return false;
}
}

View file

@ -81,8 +81,8 @@ namespace controller {
QVector<Action> getAllActions() const;
QString getActionName(Action action) const;
QString getStandardPoseName(uint16_t pose);
float getActionState(Action action) const { return _actionStates[toInt(action)]; }
bool getActionStateValid(Action action) const { return _actionStatesValid[toInt(action)]; }
float getActionState(Action action) const;
bool getActionStateValid(Action action) const;
Pose getPoseState(Action action) const;
int findAction(const QString& actionName) const;
QVector<QString> getActionNames() const;

View file

@ -34,7 +34,7 @@ class HTTPManager : public QTcpServer, public HTTPRequestHandler {
public:
/// Initializes the manager.
HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = nullptr);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
private slots:

View file

@ -1446,14 +1446,17 @@ void EntityTree::addCertifiedEntityOnServer(EntityItemPointer entity) {
entityList << entityItemID; // adds to list within hash because entityList is a reference.
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID << "total" << entityList.size() << "entities.";
}
// Delete an already-existing entity from the tree if it has the same
// Handle an already-existing entity from the tree if it has the same
// CertificateID as the entity we're trying to add.
if (!existingEntityItemID.isNull()) {
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
<< existingEntityItemID << ". Deleting existing entity.";
withWriteLock([&] {
deleteEntity(existingEntityItemID, true);
});
<< existingEntityItemID << ". No action will be taken to remove it.";
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(existingEntityItemID, true);
// });
}
}
@ -1527,10 +1530,13 @@ void EntityTree::startDynamicDomainVerificationOnServer(float minimumAgeToRemove
continue;
}
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
withWriteLock([&] {
deleteEntity(entityID, true);
});
<< "doesn't match the current Domain ID" << thisDomainID << ". No action will be taken to remove it: " << entityID;
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(entityID, true);
// });
}
{
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
@ -1555,10 +1561,13 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID)
}
});
connect(_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
qCDebug(entities) << "Ownership challenge timed out, deleting entity" << entityItemID;
withWriteLock([&] {
deleteEntity(entityItemID, true);
});
qCDebug(entities) << "Ownership challenge timed out for entity " << entityItemID << ". No action will be taken to remove it.";
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(entityItemID, true);
// });
if (_challengeOwnershipTimeoutTimer) {
_challengeOwnershipTimeoutTimer->stop();
_challengeOwnershipTimeoutTimer->deleteLater();
@ -1650,10 +1659,13 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
QByteArray text = computeNonce(entityItemID, ownerKey);
if (text == "") {
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity...";
withWriteLock([&] {
deleteEntity(entityItemID, true);
});
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. No action will be taken to remove this entity.";
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(entityItemID, true);
// });
} else {
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
// 2. Send the nonce to the rezzing avatar's node
@ -1724,15 +1736,21 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
if (networkReply->error() == QNetworkReply::NoError) {
if (!jsonObject["invalid_reason"].toString().isEmpty()) {
qCDebug(entities) << "invalid_reason not empty, deleting entity" << entityItemID;
withWriteLock([&] {
deleteEntity(entityItemID, true);
});
qCDebug(entities) << "invalid_reason not empty, no action will be taken to delete entity" << entityItemID;
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(entityItemID, true);
// });
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
qCDebug(entities) << "'transfer_status' is 'failed', deleting entity" << entityItemID;
withWriteLock([&] {
deleteEntity(entityItemID, true);
});
qCDebug(entities) << "'transfer_status' is 'failed', no action will be taken to delete entity" << entityItemID;
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(entityItemID, true);
// });
} else {
// Second, challenge ownership of the PoP cert
// (ignore pending status; a failure will be cleaned up during DDV)
@ -1742,11 +1760,14 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
senderNode);
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; no action will be taken to delete entity" << entityItemID
<< "More info:" << jsonObject;
withWriteLock([&] {
deleteEntity(entityItemID, true);
});
// FIXME: All certificate checking needs to be moved to its own files,
// then the deletion settings need to have a toggle for domain owners
// and a setting to change the verification service provider.
// withWriteLock([&] {
// deleteEntity(entityItemID, true);
// });
}
networkReply->deleteLater();

View file

@ -211,6 +211,7 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
// if it's in the error state, reset and try again.
if (_domainURL != domainURL
|| (_sockAddr.getPort() != domainPort && domainURL.scheme() == URL_SCHEME_HIFI)
|| isServerless() // For reloading content in serverless domain.
|| _isInErrorState) {
// re-set the domain info so that auth information is reloaded
hardReset("Changing domain URL");

View file

@ -33,7 +33,7 @@
#include "NetworkingConstants.h"
#include "MetaverseAPI.h"
const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
QProcessEnvironment::systemEnvironment()
.contains("HIFI_DOMAIN_SERVER_PORT")
? QProcessEnvironment::systemEnvironment()
@ -41,7 +41,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
.toUShort()
: 40102;
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
QProcessEnvironment::systemEnvironment()
.contains("HIFI_DOMAIN_SERVER_DTLS_PORT")
? QProcessEnvironment::systemEnvironment()
@ -49,7 +49,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
.toUShort()
: 40103;
const quint16 DOMAIN_SERVER_HTTP_PORT =
const quint16 DOMAIN_SERVER_HTTP_PORT =
QProcessEnvironment::systemEnvironment()
.contains("HIFI_DOMAIN_SERVER_HTTP_PORT")
? QProcessEnvironment::systemEnvironment()
@ -57,7 +57,7 @@ const quint16 DOMAIN_SERVER_HTTP_PORT =
.toUInt()
: 40100;
const quint16 DOMAIN_SERVER_HTTPS_PORT =
const quint16 DOMAIN_SERVER_HTTPS_PORT =
QProcessEnvironment::systemEnvironment()
.contains("HIFI_DOMAIN_SERVER_HTTPS_PORT")
? QProcessEnvironment::systemEnvironment()
@ -65,6 +65,15 @@ const quint16 DOMAIN_SERVER_HTTPS_PORT =
.toUInt()
: 40101;
const quint16 DOMAIN_SERVER_EXPORTER_PORT =
QProcessEnvironment::systemEnvironment()
.contains("VIRCADIA_DOMAIN_SERVER_EXPORTER_PORT")
? QProcessEnvironment::systemEnvironment()
.value("VIRCADIA_DOMAIN_SERVER_EXPORTER_PORT")
.toUInt()
: 9703;
const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5;
class DomainHandler : public QObject {

View file

@ -3,7 +3,7 @@
// libraries/networking/src
//
// Created by Kalila (kasenvr) on 2019-12-16.
// Copyright 2019 Project Athena
// Copyright 2019 Vircadia
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html

View file

@ -3,7 +3,7 @@
// libraries/networking/src
//
// Created by Kalila (kasenvr) on 2019-12-16.
// Copyright 2019 Project Athena
// Copyright 2019 Vircadia
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html

View file

@ -27,6 +27,28 @@ namespace NetworkingConstants {
const QUrl METAVERSE_SERVER_URL_STABLE { "https://metaverse.highfidelity.com" };
const QUrl METAVERSE_SERVER_URL_STAGING { "https://staging-metaverse.vircadia.com" };
// Web Engine requests to this parent domain have an account authorization header added
const QString AUTH_HOSTNAME_BASE = "highfidelity.com";
const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml");
const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml");
#if USE_STABLE_GLOBAL_SERVICES
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
#else
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
#endif
const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com";
const QUrl HELP_DOCS_URL { "https://docs.vircadia.dev" };
const QUrl HELP_FORUM_URL { "https://forums.vircadia.dev" };
const QUrl HELP_SCRIPTING_REFERENCE_URL{ "https://apidocs.vircadia.dev/" };
const QUrl HELP_RELEASE_NOTES_URL{ "https://docs.vircadia.dev/release-notes.html" };
const QUrl HELP_BUG_REPORT_URL{ "https://github.com/kasenvr/project-athena/issues" };
}
const QString HIFI_URL_SCHEME_ABOUT = "about";

View file

@ -29,7 +29,7 @@ namespace {
auto metaverseServerURL = MetaverseAPI::getCurrentMetaverseServerURL();
static const QStringList HF_HOSTS = {
"highfidelity.com", "highfidelity.io",
metaverseServerURL.toString(), "metaverse.highfidelity.io"
metaverseServerURL.toString(),
};
const auto& scheme = url.scheme();
const auto& host = url.host();

View file

@ -281,6 +281,7 @@ void LeapMotionPlugin::InputDevice::update(float deltaTime, const controller::In
glm::vec3 pos;
glm::quat rot;
glm::quat prevRot;
if (_isLeapOnHMD) {
auto jointPosition = joints[i].position;
const glm::vec3 HMD_EYE_TO_LEAP_OFFSET = glm::vec3(0.0f, 0.0f, -0.09f); // Eyes to surface of Leap Motion.
@ -291,17 +292,24 @@ void LeapMotionPlugin::InputDevice::update(float deltaTime, const controller::In
glm::quat jointOrientation = joints[i].orientation;
jointOrientation = glm::quat(jointOrientation.w, -jointOrientation.x, -jointOrientation.z, -jointOrientation.y);
rot = controllerToAvatarRotation * hmdSensorOrientation * jointOrientation;
glm::quat prevJointOrientation = prevJoints[i].orientation;
prevJointOrientation =
glm::quat(prevJointOrientation.w, -prevJointOrientation.x, -prevJointOrientation.z, -prevJointOrientation.y);
prevRot = controllerToAvatarRotation * hmdSensorOrientation * prevJointOrientation;
} else {
pos = controllerToAvatarRotation * (joints[i].position - leapMotionOffset);
const glm::quat ZERO_HAND_ORIENTATION = glm::quat(glm::vec3(PI_OVER_TWO, PI, 0.0f));
rot = controllerToAvatarRotation * joints[i].orientation * ZERO_HAND_ORIENTATION;
prevRot = controllerToAvatarRotation * prevJoints[i].orientation * ZERO_HAND_ORIENTATION;
}
glm::vec3 linearVelocity, angularVelocity;
if (i < prevJoints.size()) {
linearVelocity = (pos - (prevJoints[i].position * METERS_PER_CENTIMETER)) / deltaTime; // m/s
// quat log imaginary part points along the axis of rotation, with length of one half the angle of rotation.
glm::quat d = glm::log(rot * glm::inverse(prevJoints[i].orientation));
glm::quat d = glm::log(rot * glm::inverse(prevRot));
angularVelocity = glm::vec3(d.x, d.y, d.z) / (0.5f * deltaTime); // radians/s
}

View file

@ -15,5 +15,7 @@ if (WIN32 AND (NOT USE_GLES))
include_hifi_library_headers(octree)
target_openvr()
target_sranipal()
target_aristo()
target_link_libraries(${TARGET_NAME} Winmm.lib)
endif()

View file

@ -13,6 +13,21 @@
#include <algorithm>
#include <string>
#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4091 )
#pragma warning( disable : 4334 )
#endif
#include <SRanipal.h>
#include <SRanipal_Eye.h>
#include <SRanipal_Enums.h>
#include <interface_gesture.hpp>
#ifdef _WIN32
#pragma warning( pop )
#endif
#include <PerfStat.h>
#include <PathUtils.h>
#include <GeometryCache.h>
@ -37,6 +52,8 @@
#include <Plugins/InputConfiguration.h>
#include <controllers/StandardControls.h>
#include "OpenVrDisplayPlugin.h"
extern PoseData _nextSimPoseData;
vr::IVRSystem* acquireOpenVrSystem();
@ -62,6 +79,32 @@ static const int SECOND_FOOT = 1;
static const int HIP = 2;
static const int CHEST = 3;
enum ViveHandJointIndex {
HAND = 0,
THUMB_1,
THUMB_2,
THUMB_3,
THUMB_4,
INDEX_1,
INDEX_2,
INDEX_3,
INDEX_4,
MIDDLE_1,
MIDDLE_2,
MIDDLE_3,
MIDDLE_4,
RING_1,
RING_2,
RING_3,
RING_4,
PINKY_1,
PINKY_2,
PINKY_3,
PINKY_4,
Size
};
const char* ViveControllerManager::NAME { "OpenVR" };
const std::map<vr::ETrackingResult, QString> TRACKING_RESULT_TO_STRING = {
@ -130,6 +173,51 @@ static glm::mat4 calculateResetMat() {
return glm::mat4();
}
class ViveProEyeReadThread : public QThread {
public:
ViveProEyeReadThread() {
setObjectName("OpenVR ViveProEye Read Thread");
}
void run() override {
while (!quit) {
ViveSR::anipal::Eye::EyeData eyeData;
int result = ViveSR::anipal::Eye::GetEyeData(&eyeData);
{
QMutexLocker locker(&eyeDataMutex);
eyeDataBuffer.getEyeDataResult = result;
if (result == ViveSR::Error::WORK) {
uint64_t leftValids = eyeData.verbose_data.left.eye_data_validata_bit_mask;
uint64_t rightValids = eyeData.verbose_data.right.eye_data_validata_bit_mask;
eyeDataBuffer.leftDirectionValid =
(leftValids & (uint64_t)ViveSR::anipal::Eye::SINGLE_EYE_DATA_GAZE_DIRECTION_VALIDITY) > (uint64_t)0;
eyeDataBuffer.rightDirectionValid =
(rightValids & (uint64_t)ViveSR::anipal::Eye::SINGLE_EYE_DATA_GAZE_DIRECTION_VALIDITY) > (uint64_t)0;
eyeDataBuffer.leftOpennessValid =
(leftValids & (uint64_t)ViveSR::anipal::Eye::SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY) > (uint64_t)0;
eyeDataBuffer.rightOpennessValid =
(rightValids & (uint64_t)ViveSR::anipal::Eye::SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY) > (uint64_t)0;
float *leftGaze = eyeData.verbose_data.left.gaze_direction_normalized.elem_;
float *rightGaze = eyeData.verbose_data.right.gaze_direction_normalized.elem_;
eyeDataBuffer.leftEyeGaze = glm::vec3(leftGaze[0], leftGaze[1], leftGaze[2]);
eyeDataBuffer.rightEyeGaze = glm::vec3(rightGaze[0], rightGaze[1], rightGaze[2]);
eyeDataBuffer.leftEyeOpenness = eyeData.verbose_data.left.eye_openness;
eyeDataBuffer.rightEyeOpenness = eyeData.verbose_data.right.eye_openness;
}
}
}
}
bool quit { false };
// mutex and buffer for moving data from this thread to the other one
QMutex eyeDataMutex;
EyeDataBuffer eyeDataBuffer;
};
static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeDataStrategy strategy) {
switch (strategy) {
default:
@ -211,6 +299,81 @@ QString ViveControllerManager::configurationLayout() {
return OPENVR_LAYOUT;
}
bool isDeviceIndexActive(vr::IVRSystem*& system, uint32_t deviceIndex) {
if (!system) {
return false;
}
if (deviceIndex != vr::k_unTrackedDeviceIndexInvalid &&
system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_Controller &&
system->IsTrackedDeviceConnected(deviceIndex)) {
vr::EDeviceActivityLevel activityLevel = system->GetTrackedDeviceActivityLevel(deviceIndex);
if (activityLevel == vr::k_EDeviceActivityLevel_UserInteraction) {
return true;
}
}
return false;
}
bool isHandControllerActive(vr::IVRSystem*& system, vr::ETrackedControllerRole deviceRole) {
if (!system) {
return false;
}
auto deviceIndex = system->GetTrackedDeviceIndexForControllerRole(deviceRole);
return isDeviceIndexActive(system, deviceIndex);
}
bool areBothHandControllersActive(vr::IVRSystem*& system) {
return
isHandControllerActive(system, vr::TrackedControllerRole_LeftHand) &&
isHandControllerActive(system, vr::TrackedControllerRole_RightHand);
}
void ViveControllerManager::enableGestureDetection() {
if (_viveCameraHandTracker) {
return;
}
if (!ViveSR::anipal::Eye::IsViveProEye()) {
return;
}
// #define HAND_TRACKER_USE_EXTERNAL_TRANSFORM 1
#ifdef HAND_TRACKER_USE_EXTERNAL_TRANSFORM
UseExternalTransform(true); // camera hand tracker results are in HMD frame
#else
UseExternalTransform(false); // camera hand tracker results are in sensor frame
#endif
GestureOption options; // defaults are GestureBackendAuto and GestureModeSkeleton
GestureFailure gestureFailure = StartGestureDetection(&options);
switch (gestureFailure) {
case GestureFailureNone:
qDebug() << "StartGestureDetection success";
_viveCameraHandTracker = true;
break;
case GestureFailureOpenCL:
qDebug() << "StartGestureDetection (Only on Windows) OpenCL is not supported on the machine";
break;
case GestureFailureCamera:
qDebug() << "StartGestureDetection Start camera failed";
break;
case GestureFailureInternal:
qDebug() << "StartGestureDetection Internal errors";
break;
case GestureFailureCPUOnPC:
qDebug() << "StartGestureDetection CPU backend is not supported on Windows";
break;
}
}
void ViveControllerManager::disableGestureDetection() {
if (!_viveCameraHandTracker) {
return;
}
StopGestureDetection();
_viveCameraHandTracker = false;
}
bool ViveControllerManager::activate() {
InputPlugin::activate();
@ -230,6 +393,28 @@ bool ViveControllerManager::activate() {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->registerDevice(_inputDevice);
_registeredWithInputMapper = true;
if (ViveSR::anipal::Eye::IsViveProEye()) {
qDebug() << "Vive Pro eye-tracking detected";
int error = ViveSR::anipal::Initial(ViveSR::anipal::Eye::ANIPAL_TYPE_EYE, NULL);
if (error == ViveSR::Error::WORK) {
_viveProEye = true;
qDebug() << "Successfully initialize Eye engine.";
} else if (error == ViveSR::Error::RUNTIME_NOT_FOUND) {
_viveProEye = false;
qDebug() << "please follows SRanipal SDK guide to install SR_Runtime first";
} else {
_viveProEye = false;
qDebug() << "Failed to initialize Eye engine. please refer to ViveSR error code:" << error;
}
if (_viveProEye) {
_viveProEyeReadThread = std::make_shared<ViveProEyeReadThread>();
_viveProEyeReadThread->start(QThread::HighPriority);
}
}
return true;
}
@ -251,6 +436,13 @@ void ViveControllerManager::deactivate() {
userInputMapper->removeDevice(_inputDevice->_deviceID);
_registeredWithInputMapper = false;
if (_viveProEyeReadThread) {
_viveProEyeReadThread->quit = true;
_viveProEyeReadThread->wait();
_viveProEyeReadThread = nullptr;
ViveSR::anipal::Release(ViveSR::anipal::Eye::ANIPAL_TYPE_EYE);
}
saveSettings();
}
@ -262,6 +454,317 @@ bool ViveControllerManager::isHeadControllerMounted() const {
return activityLevel == vr::k_EDeviceActivityLevel_UserInteraction;
}
void ViveControllerManager::invalidateEyeInputs() {
_inputDevice->_poseStateMap[controller::LEFT_EYE].valid = false;
_inputDevice->_poseStateMap[controller::RIGHT_EYE].valid = false;
_inputDevice->_axisStateMap[controller::EYEBLINK_L].valid = false;
_inputDevice->_axisStateMap[controller::EYEBLINK_R].valid = false;
}
void ViveControllerManager::updateEyeTracker(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
if (!isHeadControllerMounted()) {
invalidateEyeInputs();
return;
}
EyeDataBuffer eyeDataBuffer;
{
// GetEyeData takes around 4ms to finish, so we run it on a thread.
QMutexLocker locker(&_viveProEyeReadThread->eyeDataMutex);
memcpy(&eyeDataBuffer, &_viveProEyeReadThread->eyeDataBuffer, sizeof(eyeDataBuffer));
}
if (eyeDataBuffer.getEyeDataResult != ViveSR::Error::WORK) {
invalidateEyeInputs();
return;
}
// only update from buffer values if the new data is "valid"
if (!eyeDataBuffer.leftDirectionValid) {
eyeDataBuffer.leftEyeGaze = _prevEyeData.leftEyeGaze;
eyeDataBuffer.leftDirectionValid = _prevEyeData.leftDirectionValid;
}
if (!eyeDataBuffer.rightDirectionValid) {
eyeDataBuffer.rightEyeGaze = _prevEyeData.rightEyeGaze;
eyeDataBuffer.rightDirectionValid = _prevEyeData.rightDirectionValid;
}
if (!eyeDataBuffer.leftOpennessValid) {
eyeDataBuffer.leftEyeOpenness = _prevEyeData.leftEyeOpenness;
eyeDataBuffer.leftOpennessValid = _prevEyeData.leftOpennessValid;
}
if (!eyeDataBuffer.rightOpennessValid) {
eyeDataBuffer.rightEyeOpenness = _prevEyeData.rightEyeOpenness;
eyeDataBuffer.rightOpennessValid = _prevEyeData.rightOpennessValid;
}
_prevEyeData = eyeDataBuffer;
// transform data into what the controller system expects.
// in the data from sranipal, left=+x, up=+y, forward=+z
mat4 localLeftEyeMat = glm::lookAt(vec3(0.0f, 0.0f, 0.0f),
glm::vec3(eyeDataBuffer.leftEyeGaze[0],
eyeDataBuffer.leftEyeGaze[1],
-eyeDataBuffer.leftEyeGaze[2]),
vec3(0.0f, 1.0f, 0.0f));
quat localLeftEyeRot = glm::quat_cast(localLeftEyeMat);
quat avatarLeftEyeRot = _inputDevice->_poseStateMap[controller::HEAD].rotation * localLeftEyeRot;
mat4 localRightEyeMat = glm::lookAt(vec3(0.0f, 0.0f, 0.0f),
glm::vec3(eyeDataBuffer.rightEyeGaze[0],
eyeDataBuffer.rightEyeGaze[1],
-eyeDataBuffer.rightEyeGaze[2]),
vec3(0.0f, 1.0f, 0.0f));
quat localRightEyeRot = glm::quat_cast(localRightEyeMat);
quat avatarRightEyeRot = _inputDevice->_poseStateMap[controller::HEAD].rotation * localRightEyeRot;
// TODO -- figure out translations for eyes
if (eyeDataBuffer.leftDirectionValid) {
_inputDevice->_poseStateMap[controller::LEFT_EYE] = controller::Pose(glm::vec3(), avatarLeftEyeRot);
_inputDevice->_poseStateMap[controller::LEFT_EYE].valid = true;
} else {
_inputDevice->_poseStateMap[controller::LEFT_EYE].valid = false;
}
if (eyeDataBuffer.rightDirectionValid) {
_inputDevice->_poseStateMap[controller::RIGHT_EYE] = controller::Pose(glm::vec3(), avatarRightEyeRot);
_inputDevice->_poseStateMap[controller::RIGHT_EYE].valid = true;
} else {
_inputDevice->_poseStateMap[controller::RIGHT_EYE].valid = false;
}
quint64 now = usecTimestampNow();
// in hifi, 0 is open 1 is closed. in SRanipal 1 is open, 0 is closed.
if (eyeDataBuffer.leftOpennessValid) {
_inputDevice->_axisStateMap[controller::EYEBLINK_L] =
controller::AxisValue(1.0f - eyeDataBuffer.leftEyeOpenness, now);
} else {
_inputDevice->_poseStateMap[controller::EYEBLINK_L].valid = false;
}
if (eyeDataBuffer.rightOpennessValid) {
_inputDevice->_axisStateMap[controller::EYEBLINK_R] =
controller::AxisValue(1.0f - eyeDataBuffer.rightEyeOpenness, now);
} else {
_inputDevice->_poseStateMap[controller::EYEBLINK_R].valid = false;
}
}
glm::vec3 ViveControllerManager::getRollingAverageHandPoint(int handIndex, int pointIndex) const {
#if 0
return _handPoints[0][handIndex][pointIndex];
#else
glm::vec3 result;
for (int s = 0; s < NUMBER_OF_HAND_TRACKER_SMOOTHING_FRAMES; s++) {
result += _handPoints[s][handIndex][pointIndex];
}
return result / NUMBER_OF_HAND_TRACKER_SMOOTHING_FRAMES;
#endif
}
controller::Pose ViveControllerManager::trackedHandDataToPose(int hand, const glm::vec3& palmFacing,
int nearHandPositionIndex, int farHandPositionIndex) {
glm::vec3 nearPoint = getRollingAverageHandPoint(hand, nearHandPositionIndex);
glm::quat poseRot;
if (nearHandPositionIndex != farHandPositionIndex) {
glm::vec3 farPoint = getRollingAverageHandPoint(hand, farHandPositionIndex);
glm::vec3 pointingDir = farPoint - nearPoint; // y axis
glm::vec3 otherAxis = glm::cross(pointingDir, palmFacing);
glm::mat4 rotMat;
rotMat = glm::mat4(glm::vec4(otherAxis, 0.0f),
glm::vec4(pointingDir, 0.0f),
glm::vec4(palmFacing * (hand == 0 ? 1.0f : -1.0f), 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
poseRot = glm::normalize(glmExtractRotation(rotMat));
}
if (!isNaN(poseRot)) {
controller::Pose pose(nearPoint, poseRot);
return pose;
} else {
controller::Pose pose;
pose.valid = false;
return pose;
}
}
void ViveControllerManager::trackFinger(int hand, int jointIndex1, int jointIndex2, int jointIndex3, int jointIndex4,
controller::StandardPoseChannel joint1, controller::StandardPoseChannel joint2,
controller::StandardPoseChannel joint3, controller::StandardPoseChannel joint4) {
glm::vec3 point1 = getRollingAverageHandPoint(hand, jointIndex1);
glm::vec3 point2 = getRollingAverageHandPoint(hand, jointIndex2);
glm::vec3 point3 = getRollingAverageHandPoint(hand, jointIndex3);
glm::vec3 point4 = getRollingAverageHandPoint(hand, jointIndex4);
glm::vec3 wristPos = getRollingAverageHandPoint(hand, ViveHandJointIndex::HAND);
glm::vec3 thumb2 = getRollingAverageHandPoint(hand, ViveHandJointIndex::THUMB_2);
glm::vec3 pinkie1 = getRollingAverageHandPoint(hand, ViveHandJointIndex::PINKY_1);
// 1st
glm::vec3 palmFacing = glm::normalize(glm::cross(pinkie1 - wristPos, thumb2 - wristPos));
glm::vec3 handForward = glm::normalize(point1 - wristPos);
glm::vec3 x = glm::normalize(glm::cross(palmFacing, handForward));
glm::vec3 y = glm::normalize(point2 - point1);
glm::vec3 z = (hand == 0) ? glm::cross(y, x) : glm::cross(x, y);
glm::mat4 rotMat1 = glm::mat4(glm::vec4(x, 0.0f),
glm::vec4(y, 0.0f),
glm::vec4(z, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
glm::quat rot1 = glm::normalize(glmExtractRotation(rotMat1));
if (!isNaN(rot1)) {
_inputDevice->_poseStateMap[joint1] = controller::Pose(point1, rot1);
}
// 2nd
glm::vec3 x2 = x; // glm::normalize(glm::cross(point3 - point2, point2 - point1));
glm::vec3 y2 = glm::normalize(point3 - point2);
glm::vec3 z2 = (hand == 0) ? glm::cross(y2, x2) : glm::cross(x2, y2);
glm::mat4 rotMat2 = glm::mat4(glm::vec4(x2, 0.0f),
glm::vec4(y2, 0.0f),
glm::vec4(z2, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
glm::quat rot2 = glm::normalize(glmExtractRotation(rotMat2));
if (!isNaN(rot2)) {
_inputDevice->_poseStateMap[joint2] = controller::Pose(point2, rot2);
}
// 3rd
glm::vec3 x3 = x; // glm::normalize(glm::cross(point4 - point3, point3 - point1));
glm::vec3 y3 = glm::normalize(point4 - point3);
glm::vec3 z3 = (hand == 0) ? glm::cross(y3, x3) : glm::cross(x3, y3);
glm::mat4 rotMat3 = glm::mat4(glm::vec4(x3, 0.0f),
glm::vec4(y3, 0.0f),
glm::vec4(z3, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
glm::quat rot3 = glm::normalize(glmExtractRotation(rotMat3));
if (!isNaN(rot3)) {
_inputDevice->_poseStateMap[joint3] = controller::Pose(point3, rot3);
}
// 4th
glm::quat rot4 = rot3;
if (!isNaN(rot4)) {
_inputDevice->_poseStateMap[joint4] = controller::Pose(point4, rot4);
}
}
void ViveControllerManager::updateCameraHandTracker(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData) {
if (areBothHandControllersActive(_system)) {
// if both hand-controllers are in use, don't do camera hand tracking
disableGestureDetection();
} else {
enableGestureDetection();
}
if (!_viveCameraHandTracker) {
return;
}
const GestureResult* results = NULL;
int handTrackerFrameIndex { -1 };
int resultsHandCount = GetGestureResult(&results, &handTrackerFrameIndex);
// FIXME: Why the commented-out condition?
if (handTrackerFrameIndex >= 0 /* && handTrackerFrameIndex != _lastHandTrackerFrameIndex */) {
#ifdef HAND_TRACKER_USE_EXTERNAL_TRANSFORM
glm::mat4 trackedHandToAvatar =
glm::inverse(inputCalibrationData.avatarMat) *
inputCalibrationData.sensorToWorldMat *
inputCalibrationData.hmdSensorMat;
// glm::mat4 trackedHandToAvatar = _inputDevice->_poseStateMap[controller::HEAD].getMatrix() * Matrices::Y_180;
#else
DisplayPluginPointer displayPlugin = _container->getActiveDisplayPlugin();
std::shared_ptr<OpenVrDisplayPlugin> openVRDisplayPlugin =
std::dynamic_pointer_cast<OpenVrDisplayPlugin>(displayPlugin);
glm::mat4 sensorResetMatrix;
if (openVRDisplayPlugin) {
sensorResetMatrix = openVRDisplayPlugin->getSensorResetMatrix();
}
glm::mat4 trackedHandToAvatar =
glm::inverse(inputCalibrationData.avatarMat) *
inputCalibrationData.sensorToWorldMat *
sensorResetMatrix;
#endif
// roll all the old points in the rolling average
memmove(&(_handPoints[1]),
&(_handPoints[0]),
sizeof(_handPoints[0]) * (NUMBER_OF_HAND_TRACKER_SMOOTHING_FRAMES - 1));
for (int handIndex = 0; handIndex < resultsHandCount; handIndex++) {
bool isLeftHand = results[handIndex].isLeft;
vr::ETrackedControllerRole controllerRole =
isLeftHand ? vr::TrackedControllerRole_LeftHand : vr::TrackedControllerRole_RightHand;
if (isHandControllerActive(_system, controllerRole)) {
continue; // if the controller for this hand is tracked, ignore camera hand tracking
}
int hand = isLeftHand ? 0 : 1;
for (int pointIndex = 0; pointIndex < NUMBER_OF_HAND_POINTS; pointIndex++) {
glm::vec3 pos(results[handIndex].points[3 * pointIndex],
results[handIndex].points[3 * pointIndex + 1],
-results[handIndex].points[3 * pointIndex + 2]);
_handPoints[0][hand][pointIndex] = transformPoint(trackedHandToAvatar, pos);
}
glm::vec3 wristPos = getRollingAverageHandPoint(hand, ViveHandJointIndex::HAND);
glm::vec3 thumb2 = getRollingAverageHandPoint(hand, ViveHandJointIndex::THUMB_2);
glm::vec3 pinkie1 = getRollingAverageHandPoint(hand, ViveHandJointIndex::PINKY_1);
glm::vec3 palmFacing = glm::cross(pinkie1 - wristPos, thumb2 - wristPos); // z axis
_inputDevice->_poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] =
trackedHandDataToPose(hand, palmFacing, ViveHandJointIndex::HAND, ViveHandJointIndex::MIDDLE_1);
trackFinger(hand, ViveHandJointIndex::THUMB_1, ViveHandJointIndex::THUMB_2, ViveHandJointIndex::THUMB_3,
ViveHandJointIndex::THUMB_4,
isLeftHand ? controller::LEFT_HAND_THUMB1 : controller::RIGHT_HAND_THUMB1,
isLeftHand ? controller::LEFT_HAND_THUMB2 : controller::RIGHT_HAND_THUMB2,
isLeftHand ? controller::LEFT_HAND_THUMB3 : controller::RIGHT_HAND_THUMB3,
isLeftHand ? controller::LEFT_HAND_THUMB4 : controller::RIGHT_HAND_THUMB4);
trackFinger(hand, ViveHandJointIndex::INDEX_1, ViveHandJointIndex::INDEX_2, ViveHandJointIndex::INDEX_3,
ViveHandJointIndex::INDEX_4,
isLeftHand ? controller::LEFT_HAND_INDEX1 : controller::RIGHT_HAND_INDEX1,
isLeftHand ? controller::LEFT_HAND_INDEX2 : controller::RIGHT_HAND_INDEX2,
isLeftHand ? controller::LEFT_HAND_INDEX3 : controller::RIGHT_HAND_INDEX3,
isLeftHand ? controller::LEFT_HAND_INDEX4 : controller::RIGHT_HAND_INDEX4);
trackFinger(hand, ViveHandJointIndex::MIDDLE_1, ViveHandJointIndex::MIDDLE_2, ViveHandJointIndex::MIDDLE_3,
ViveHandJointIndex::MIDDLE_4,
isLeftHand ? controller::LEFT_HAND_MIDDLE1 : controller::RIGHT_HAND_MIDDLE1,
isLeftHand ? controller::LEFT_HAND_MIDDLE2 : controller::RIGHT_HAND_MIDDLE2,
isLeftHand ? controller::LEFT_HAND_MIDDLE3 : controller::RIGHT_HAND_MIDDLE3,
isLeftHand ? controller::LEFT_HAND_MIDDLE4 : controller::RIGHT_HAND_MIDDLE4);
trackFinger(hand, ViveHandJointIndex::RING_1, ViveHandJointIndex::RING_2, ViveHandJointIndex::RING_3,
ViveHandJointIndex::RING_4,
isLeftHand ? controller::LEFT_HAND_RING1 : controller::RIGHT_HAND_RING1,
isLeftHand ? controller::LEFT_HAND_RING2 : controller::RIGHT_HAND_RING2,
isLeftHand ? controller::LEFT_HAND_RING3 : controller::RIGHT_HAND_RING3,
isLeftHand ? controller::LEFT_HAND_RING4 : controller::RIGHT_HAND_RING4);
trackFinger(hand, ViveHandJointIndex::PINKY_1, ViveHandJointIndex::PINKY_2, ViveHandJointIndex::PINKY_3,
ViveHandJointIndex::PINKY_4,
isLeftHand ? controller::LEFT_HAND_PINKY1 : controller::RIGHT_HAND_PINKY1,
isLeftHand ? controller::LEFT_HAND_PINKY2 : controller::RIGHT_HAND_PINKY2,
isLeftHand ? controller::LEFT_HAND_PINKY3 : controller::RIGHT_HAND_PINKY3,
isLeftHand ? controller::LEFT_HAND_PINKY4 : controller::RIGHT_HAND_PINKY4);
}
}
_lastHandTrackerFrameIndex = handTrackerFrameIndex;
}
void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
if (!_system) {
@ -297,6 +800,12 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu
userInputMapper->registerDevice(_inputDevice);
_registeredWithInputMapper = true;
}
if (_viveProEye) {
updateEyeTracker(deltaTime, inputCalibrationData);
}
updateCameraHandTracker(deltaTime, inputCalibrationData);
}
void ViveControllerManager::loadSettings() {
@ -830,9 +1339,7 @@ void ViveControllerManager::InputDevice::handleHmd(uint32_t deviceIndex, const c
void ViveControllerManager::InputDevice::handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand) {
if (_system->IsTrackedDeviceConnected(deviceIndex) &&
_system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_Controller &&
_nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid) {
if (isDeviceIndexActive(_system, deviceIndex) && _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid) {
// process pose
const mat4& mat = _nextSimPoseData.poses[deviceIndex];
@ -1401,9 +1908,52 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI
makePair(LEFT_GRIP, "LeftGrip"),
makePair(RIGHT_GRIP, "RightGrip"),
// 3d location of controller
// 3d location of left controller and fingers
makePair(LEFT_HAND, "LeftHand"),
makePair(LEFT_HAND_THUMB1, "LeftHandThumb1"),
makePair(LEFT_HAND_THUMB2, "LeftHandThumb2"),
makePair(LEFT_HAND_THUMB3, "LeftHandThumb3"),
makePair(LEFT_HAND_THUMB4, "LeftHandThumb4"),
makePair(LEFT_HAND_INDEX1, "LeftHandIndex1"),
makePair(LEFT_HAND_INDEX2, "LeftHandIndex2"),
makePair(LEFT_HAND_INDEX3, "LeftHandIndex3"),
makePair(LEFT_HAND_INDEX4, "LeftHandIndex4"),
makePair(LEFT_HAND_MIDDLE1, "LeftHandMiddle1"),
makePair(LEFT_HAND_MIDDLE2, "LeftHandMiddle2"),
makePair(LEFT_HAND_MIDDLE3, "LeftHandMiddle3"),
makePair(LEFT_HAND_MIDDLE4, "LeftHandMiddle4"),
makePair(LEFT_HAND_RING1, "LeftHandRing1"),
makePair(LEFT_HAND_RING2, "LeftHandRing2"),
makePair(LEFT_HAND_RING3, "LeftHandRing3"),
makePair(LEFT_HAND_RING4, "LeftHandRing4"),
makePair(LEFT_HAND_PINKY1, "LeftHandPinky1"),
makePair(LEFT_HAND_PINKY2, "LeftHandPinky2"),
makePair(LEFT_HAND_PINKY3, "LeftHandPinky3"),
makePair(LEFT_HAND_PINKY4, "LeftHandPinky4"),
// 3d location of right controller and fingers
makePair(RIGHT_HAND, "RightHand"),
makePair(RIGHT_HAND_THUMB1, "RightHandThumb1"),
makePair(RIGHT_HAND_THUMB2, "RightHandThumb2"),
makePair(RIGHT_HAND_THUMB3, "RightHandThumb3"),
makePair(RIGHT_HAND_THUMB4, "RightHandThumb4"),
makePair(RIGHT_HAND_INDEX1, "RightHandIndex1"),
makePair(RIGHT_HAND_INDEX2, "RightHandIndex2"),
makePair(RIGHT_HAND_INDEX3, "RightHandIndex3"),
makePair(RIGHT_HAND_INDEX4, "RightHandIndex4"),
makePair(RIGHT_HAND_MIDDLE1, "RightHandMiddle1"),
makePair(RIGHT_HAND_MIDDLE2, "RightHandMiddle2"),
makePair(RIGHT_HAND_MIDDLE3, "RightHandMiddle3"),
makePair(RIGHT_HAND_MIDDLE4, "RightHandMiddle4"),
makePair(RIGHT_HAND_RING1, "RightHandRing1"),
makePair(RIGHT_HAND_RING2, "RightHandRing2"),
makePair(RIGHT_HAND_RING3, "RightHandRing3"),
makePair(RIGHT_HAND_RING4, "RightHandRing4"),
makePair(RIGHT_HAND_PINKY1, "RightHandPinky1"),
makePair(RIGHT_HAND_PINKY2, "RightHandPinky2"),
makePair(RIGHT_HAND_PINKY3, "RightHandPinky3"),
makePair(RIGHT_HAND_PINKY4, "RightHandPinky4"),
makePair(LEFT_FOOT, "LeftFoot"),
makePair(RIGHT_FOOT, "RightFoot"),
makePair(HIPS, "Hips"),
@ -1411,6 +1961,10 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI
makePair(HEAD, "Head"),
makePair(LEFT_ARM, "LeftArm"),
makePair(RIGHT_ARM, "RightArm"),
makePair(LEFT_EYE, "LeftEye"),
makePair(RIGHT_EYE, "RightEye"),
makePair(EYEBLINK_L, "EyeBlink_L"),
makePair(EYEBLINK_R, "EyeBlink_R"),
// 16 tracked poses
makePair(TRACKED_OBJECT_00, "TrackedObject00"),

View file

@ -31,6 +31,23 @@ namespace vr {
class IVRSystem;
}
class ViveProEyeReadThread;
class EyeDataBuffer {
public:
int getEyeDataResult { 0 };
bool leftDirectionValid { false };
bool rightDirectionValid { false };
bool leftOpennessValid { false };
bool rightOpennessValid { false };
glm::vec3 leftEyeGaze;
glm::vec3 rightEyeGaze;
float leftEyeOpenness { 0.0f };
float rightEyeOpenness { 0.0f };
};
class ViveControllerManager : public InputPlugin {
Q_OBJECT
public:
@ -49,12 +66,18 @@ public:
bool isHeadController() const override { return true; }
bool isHeadControllerMounted() const;
void enableGestureDetection();
void disableGestureDetection();
bool activate() override;
void deactivate() override;
QString getDeviceName() { return QString::fromStdString(_inputDevice->_headsetName); }
void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
void invalidateEyeInputs();
void updateEyeTracker(float deltaTime, const controller::InputCalibrationData& inputCalibrationData);
void updateCameraHandTracker(float deltaTime, const controller::InputCalibrationData& inputCalibrationData);
void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
virtual void saveSettings() const override;
@ -229,6 +252,23 @@ private:
vr::IVRSystem* _system { nullptr };
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>(_system) };
bool _viveProEye { false };
std::shared_ptr<ViveProEyeReadThread> _viveProEyeReadThread;
EyeDataBuffer _prevEyeData;
bool _viveCameraHandTracker { false };
int _lastHandTrackerFrameIndex { -1 };
const static int NUMBER_OF_HAND_TRACKER_SMOOTHING_FRAMES { 6 };
const static int NUMBER_OF_HAND_POINTS { 21 };
glm::vec3 _handPoints[NUMBER_OF_HAND_TRACKER_SMOOTHING_FRAMES][2][NUMBER_OF_HAND_POINTS]; // 2 for number of hands
glm::vec3 getRollingAverageHandPoint(int handIndex, int pointIndex) const;
controller::Pose trackedHandDataToPose(int hand, const glm::vec3& palmFacing,
int nearHandPositionIndex, int farHandPositionIndex);
void trackFinger(int hand, int jointIndex1, int jointIndex2, int jointIndex3, int jointIndex4,
controller::StandardPoseChannel joint1, controller::StandardPoseChannel joint2,
controller::StandardPoseChannel joint3, controller::StandardPoseChannel joint4);
static const char* NAME;
};

View file

@ -35,7 +35,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
var DEBUG = false;
var SHOW_GRAB_SPHERE = false;
if (typeof Test !== "undefined") {
PROFILE = true;
}
@ -54,6 +53,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.pointerManager = new PointerManager();
this.grabSphereOverlays = [null, null];
this.targetIDs = {};
this.debugPanelID = null;
this.debugLines = [];
// a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are
// not set to false (not in use), a module cannot start. When a module is using a slot, that module's name
@ -97,11 +98,15 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
this.runningPluginNames = {};
this.leftTriggerValue = 0;
this.leftTriggerClicked = 0;
this.leftTrackerClicked = false; // is leftTriggerClicked == 1 because a hand tracker set it?
this.leftSecondaryValue = 0;
this.rightTriggerValue = 0;
this.rightTriggerClicked = 0;
this.leftSecondaryValue = 0;
this.rightTrackerClicked = false; // is rightTriggerClicked == 1 because a hand tracker set it?
this.rightSecondaryValue = 0;
this.leftTriggerPress = function (value) {
@ -162,6 +167,38 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
};
this.checkForHandTrackingClick = function() {
var pinchOnBelowDistance = 0.016;
var pinchOffAboveDistance = 0.035;
var leftIndexPose = Controller.getPoseValue(Controller.Standard.LeftHandIndex4);
var leftThumbPose = Controller.getPoseValue(Controller.Standard.LeftHandThumb4);
var leftThumbToIndexDistance = Vec3.distance(leftIndexPose.translation, leftThumbPose.translation);
if (leftIndexPose.valid && leftThumbPose.valid && leftThumbToIndexDistance < pinchOnBelowDistance) {
_this.leftTriggerClicked = 1;
_this.leftTriggerValue = 1;
_this.leftTrackerClicked = true;
} else if (_this.leftTrackerClicked && leftThumbToIndexDistance > pinchOffAboveDistance) {
_this.leftTriggerClicked = 0;
_this.leftTriggerValue = 0;
_this.leftTrackerClicked = false;
}
var rightIndexPose = Controller.getPoseValue(Controller.Standard.RightHandIndex4);
var rightThumbPose = Controller.getPoseValue(Controller.Standard.RightHandThumb4);
var rightThumbToIndexDistance = Vec3.distance(rightIndexPose.translation, rightThumbPose.translation);
if (rightIndexPose.valid && rightThumbPose.valid && rightThumbToIndexDistance < pinchOnBelowDistance) {
_this.rightTriggerClicked = 1;
_this.rightTriggerValue = 1;
_this.rightTrackerClicked = true;
} else if (_this.rightTrackerClicked && rightThumbToIndexDistance > pinchOffAboveDistance) {
_this.rightTriggerClicked = 0;
_this.rightTriggerValue = 0;
_this.rightTrackerClicked = false;
}
};
this.update = function () {
try {
_this.updateInternal();
@ -171,6 +208,18 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS);
};
this.addDebugLine = function(line) {
if (this.debugLines.length > 8) {
this.debugLines.shift();
}
this.debugLines.push(line);
var debugPanelText = "";
this.debugLines.forEach(function(debugLine) {
debugPanelText += debugLine + "\n";
});
Entities.editEntity(this.debugPanelID, { text: debugPanelText });
};
this.updateInternal = function () {
if (PROFILE) {
Script.beginProfileRange("dispatch.pre");
@ -274,6 +323,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
var nearbyEntityIDs = Entities.findEntities(controllerPosition, findRadius);
for (var j = 0; j < nearbyEntityIDs.length; j++) {
var entityID = nearbyEntityIDs[j];
var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
@ -369,6 +419,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
}
// check for hand-tracking "click"
_this.checkForHandTrackingClick();
// bundle up all the data about the current situation
var controllerData = {
triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue],
@ -406,7 +459,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
_this.markSlots(candidatePlugin, orderedPluginName);
_this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser);
if (DEBUG) {
print("controllerDispatcher running " + orderedPluginName);
_this.addDebugLine("running " + orderedPluginName);
}
}
if (PROFILE) {
@ -438,8 +491,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
if (DEBUG) {
if (JSON.stringify(_this.targetIDs[runningPluginName]) != JSON.stringify(runningness.targets)) {
print("controllerDispatcher targetIDs[" + runningPluginName + "] = " +
JSON.stringify(runningness.targets));
_this.addDebugLine("targetIDs[" + runningPluginName + "] = " +
JSON.stringify(runningness.targets));
}
}
@ -450,12 +503,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
delete _this.runningPluginNames[runningPluginName];
delete _this.targetIDs[runningPluginName];
if (DEBUG) {
print("controllerDispatcher deleted targetIDs[" + runningPluginName + "]");
_this.addDebugLine("deleted targetIDs[" + runningPluginName + "]");
}
_this.markSlots(plugin, false);
_this.pointerManager.makePointerInvisible(plugin.parameters.handLaser);
if (DEBUG) {
print("controllerDispatcher stopping " + runningPluginName);
_this.addDebugLine("stopping " + runningPluginName);
}
}
_this.pointerManager.lockPointerEnd(plugin.parameters.handLaser, runningness.laserLockInfo);
@ -599,7 +652,33 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Overlays.mousePressOnOverlay.disconnect(mousePress);
Entities.mousePressOnEntity.disconnect(mousePress);
Messages.messageReceived.disconnect(controllerDispatcher.handleMessage);
if (_this.debugPanelID) {
Entities.deleteEntity(_this.debugPanelID);
_this.debugPanelID = null;
}
};
if (DEBUG) {
this.debugPanelID = Entities.addEntity({
name: "controllerDispatcher debug panel",
type: "Text",
dimensions: { x: 1.0, y: 0.3, z: 0.01 },
parentID: MyAvatar.sessionUUID,
// parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"),
parentJointIndex: -1,
localPosition: { x: -0.25, y: 0.8, z: -1.2 },
textColor: { red: 255, green: 255, blue: 255},
backgroundColor: { red: 0, green: 0, blue: 0},
text: "",
lineHeight: 0.03,
leftMargin: 0.015,
topMargin: 0.01,
backgroundAlpha: 0.7,
textAlpha: 1.0,
unlit: true,
ignorePickIntersection: true
}, "local");
}
}
function mouseReleaseOnOverlay(overlayID, event) {
@ -629,6 +708,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');
Messages.messageReceived.connect(controllerDispatcher.handleMessage);
Script.scriptEnding.connect(controllerDispatcher.cleanup);
Script.scriptEnding.connect(function () {
controllerDispatcher.cleanup();
});
Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS);
}());

View file

@ -17,7 +17,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.hand = hand;
this.disableModules = false;
this.parameters = makeDispatcherModuleParameters(
90,
95,
this.hand === RIGHT_HAND ?
["rightHand", "rightHandEquip", "rightHandTrigger"] :
["leftHand", "leftHandEquip", "leftHandTrigger"],

View file

@ -14,7 +14,7 @@
TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD,
Picks, makeLaserLockInfo, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST,
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES, Uuid, Picks
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES, Uuid, Picks, handsAreTracked, Messages
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -374,6 +374,9 @@ Script.include("/~/system/libraries/controllers.js");
this.isReady = function (controllerData) {
if (HMD.active) {
if (handsAreTracked()) {
return makeRunningValues(false, [], []);
}
if (this.notPointingAtEntity(controllerData)) {
return makeRunningValues(false, [], []);
}

View file

@ -12,7 +12,7 @@
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC,
projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager,
getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGrabbableGroupParent,
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES, handsAreTracked
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -63,7 +63,6 @@ Script.include("/~/system/libraries/controllers.js");
this.endedGrab = 0;
this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms
this.disabled = false;
var _this = this;
this.initialControllerRotation = Quat.IDENTITY;
this.currentControllerRotation = Quat.IDENTITY;
this.manipulating = false;
@ -99,7 +98,7 @@ Script.include("/~/system/libraries/controllers.js");
this.getOffhand = function () {
return (this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND);
}
};
// Activation criteria for rotating a fargrabbed entity. If we're changing the mapping, this is where to do it.
this.shouldManipulateTarget = function (controllerData) {
@ -406,6 +405,9 @@ Script.include("/~/system/libraries/controllers.js");
this.isReady = function (controllerData) {
if (HMD.active) {
if (handsAreTracked()) {
return makeRunningValues(false, [], []);
}
if (this.notPointingAtEntity(controllerData)) {
return makeRunningValues(false, [], []);
}

View file

@ -8,7 +8,7 @@
/* global Script, RIGHT_HAND, LEFT_HAND, MyAvatar,
makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters,
getGrabbableData, makeLaserParams, DISPATCHER_PROPERTIES
getGrabbableData, makeLaserParams, DISPATCHER_PROPERTIES, RayPick, handsAreTracked
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -63,6 +63,9 @@ Script.include("/~/system/libraries/controllers.js");
this.isReady = function (controllerData) {
this.targetEntityID = null;
if (handsAreTracked()) {
return makeRunningValues(false, [], []);
}
if (controllerData.triggerClicks[this.hand] === 0) {
return makeRunningValues(false, [], []);
}

View file

@ -151,7 +151,7 @@ Script.include("/~/system/libraries/controllers.js");
this.run = function (controllerData, deltaTime) {
if (this.grabbing) {
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
if (!controllerData.triggerClicks[this.hand] &&
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
this.endNearGrabEntity();
return makeRunningValues(false, [], []);

View file

@ -7,7 +7,7 @@
/* global Script, MyAvatar, Controller, Uuid, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
makeRunningValues, Vec3, makeDispatcherModuleParameters, Overlays, HMD, Settings, getEnabledModuleByName, Pointers,
Picks, PickType
Picks, PickType, Keyboard
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -52,22 +52,21 @@ Script.include("/~/system/libraries/controllers.js");
this.disable = false;
this.otherModuleNeedsToRun = function(controllerData) {
var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
var grabOverlayModule = getEnabledModuleByName(grabOverlayModuleName);
var grabEntityModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity";
var grabEntityModuleName = this.hand === RIGHT_HAND ? "RightNearGrabEntity" : "LeftNearGrabEntity";
var grabEntityModule = getEnabledModuleByName(grabEntityModuleName);
var grabOverlayModuleReady = grabOverlayModule ? grabOverlayModule.isReady(controllerData) : makeRunningValues(false, [], []);
var grabEntityModuleReady = grabEntityModule ? grabEntityModule.isReady(controllerData) : makeRunningValues(false, [], []);
var farGrabModuleName = this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity";
var farGrabModuleName = this.hand === RIGHT_HAND ? "RightFarGrabEntity" : "LeftFarGrabEntity";
var farGrabModule = getEnabledModuleByName(farGrabModuleName);
var farGrabModuleReady = farGrabModule ? farGrabModule.isReady(controllerData) : makeRunningValues(false, [], []);
var nearTabletHighlightModuleName =
this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight";
var nearTabletHighlightModule = getEnabledModuleByName(nearTabletHighlightModuleName);
var nearTabletHighlightModuleReady = nearTabletHighlightModule
? nearTabletHighlightModule.isReady(controllerData) : makeRunningValues(false, [], []);
return grabOverlayModuleReady.active || farGrabModuleReady.active || grabEntityModuleReady.active
|| nearTabletHighlightModuleReady.active;
var nearTabletHighlightModuleReady = nearTabletHighlightModule ?
nearTabletHighlightModule.isReady(controllerData) : makeRunningValues(false, [], []);
return farGrabModuleReady.active || grabEntityModuleReady.active;
};
this.overlayLaserActive = function(controllerData) {
@ -121,14 +120,15 @@ Script.include("/~/system/libraries/controllers.js");
}
// Add the mini tablet.
if (HMD.miniTabletScreenID && Overlays.getProperty(HMD.miniTabletScreenID, "visible")) {
if (HMD.miniTabletScreenID && Overlays.getProperty(HMD.miniTabletScreenID, "visible") &&
this.hand != HMD.miniTabletHand) {
stylusTarget = getOverlayDistance(controllerPosition, HMD.miniTabletScreenID);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
const WEB_DISPLAY_STYLUS_DISTANCE = (Keyboard.raised && Keyboard.preferMalletsOverLasers) ? 0.2 : 0.5;
var WEB_DISPLAY_STYLUS_DISTANCE = (Keyboard.raised && Keyboard.preferMalletsOverLasers) ? 0.2 : 0.5;
var nearStylusTarget = isNearStylusTarget(stylusTargets, WEB_DISPLAY_STYLUS_DISTANCE * sensorScaleFactor);
if (nearStylusTarget.length !== 0) {

View file

@ -0,0 +1,136 @@
"use strict";
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, makeRunningValues, enableDispatcherModule, disableDispatcherModule,
makeDispatcherModuleParameters, handsAreTracked, Controller, Vec3, Tablet, HMD, MyAvatar
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
(function() {
function TrackedHandTablet() {
this.mappingName = 'hand-track-tablet-' + Math.random();
this.inputMapping = Controller.newMapping(this.mappingName);
this.leftIndexPos = null;
this.leftThumbPos = null;
this.rightIndexPos = null;
this.rightThumbPos = null;
this.touchOnBelowDistance = 0.016;
this.touchOffAboveDistance = 0.045;
this.gestureCompleted = false;
this.previousGestureCompleted = false;
this.parameters = makeDispatcherModuleParameters(
70,
["rightHand", "leftHand"],
[],
100);
this.checkForGesture = function () {
if (this.leftThumbPos && this.leftIndexPos && this.rightThumbPos && this.rightIndexPos) {
var leftTipDistance = Vec3.distance(this.leftThumbPos, this.leftIndexPos);
var rightTipDistance = Vec3.distance(this.rightThumbPos, this.rightIndexPos);
if (leftTipDistance < this.touchOnBelowDistance && rightTipDistance < this.touchOnBelowDistance) {
this.gestureCompleted = true;
} else if (leftTipDistance > this.touchOffAboveDistance || rightTipDistance > this.touchOffAboveDistance) {
this.gestureCompleted = false;
} // else don't change gestureCompleted
} else {
this.gestureCompleted = false;
}
if (this.gestureCompleted && !this.previousGestureCompleted) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (HMD.showTablet) {
HMD.closeTablet(false);
} else if (!HMD.showTablet && !tablet.toolbarMode && !MyAvatar.isAway) {
tablet.gotoHomeScreen();
HMD.openTablet(false);
}
}
this.previousGestureCompleted = this.gestureCompleted;
};
this.leftIndexChanged = function (pose) {
if (pose.valid) {
this.leftIndexPos = pose.translation;
} else {
this.leftIndexPos = null;
}
this.checkForGesture();
};
this.leftThumbChanged = function (pose) {
if (pose.valid) {
this.leftThumbPos = pose.translation;
} else {
this.leftThumbPos = null;
}
this.checkForGesture();
};
this.rightIndexChanged = function (pose) {
if (pose.valid) {
this.rightIndexPos = pose.translation;
} else {
this.rightIndexPos = null;
}
this.checkForGesture();
};
this.rightThumbChanged = function (pose) {
if (pose.valid) {
this.rightThumbPos = pose.translation;
} else {
this.rightThumbPos = null;
}
this.checkForGesture();
};
this.isReady = function (controllerData) {
return makeRunningValues(handsAreTracked() && this.gestureCompleted, [], []);
};
this.run = function (controllerData) {
return this.isReady(controllerData);
};
this.setup = function () {
var _this = this;
this.inputMapping.from(Controller.Standard.LeftHandIndex4).peek().to(function (pose) {
_this.leftIndexChanged(pose);
});
this.inputMapping.from(Controller.Standard.LeftHandThumb4).peek().to(function (pose) {
_this.leftThumbChanged(pose);
});
this.inputMapping.from(Controller.Standard.RightHandIndex4).peek().to(function (pose) {
_this.rightIndexChanged(pose);
});
this.inputMapping.from(Controller.Standard.RightHandThumb4).peek().to(function (pose) {
_this.rightThumbChanged(pose);
});
Controller.enableMapping(this.mappingName);
};
this.cleanUp = function () {
this.inputMapping.disable();
};
}
var trackedHandWalk = new TrackedHandTablet();
trackedHandWalk.setup();
enableDispatcherModule("TrackedHandTablet", trackedHandWalk);
function cleanup() {
trackedHandWalk.cleanUp();
disableDispatcherModule("TrackedHandTablet");
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -0,0 +1,172 @@
"use strict";
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, makeRunningValues, enableDispatcherModule, disableDispatcherModule,
makeDispatcherModuleParameters, handsAreTracked, Controller, Vec3
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
(function() {
function TrackedHandWalk() {
this.gestureMappingName = 'hand-track-walk-gesture-' + Math.random();
this.inputGestureMapping = Controller.newMapping(this.gestureMappingName);
this.mappingName = 'hand-track-walk-' + Math.random();
this.inputMapping = Controller.newMapping(this.mappingName);
this.leftIndexPos = null;
this.leftThumbPos = null;
this.rightIndexPos = null;
this.rightThumbPos = null;
this.touchOnBelowDistance = 0.016;
this.touchOffAboveDistance = 0.045;
this.walkingForward = false;
this.walkingBackward = false;
this.mappingEnabled = false;
this.parameters = makeDispatcherModuleParameters(
80,
["rightHand", "leftHand"],
[],
100);
this.getControlPoint = function () {
return Vec3.multiply(Vec3.sum(this.leftIndexPos, this.rightIndexPos), 0.5);
};
this.updateWalking = function () {
if (this.leftIndexPos && this.rightIndexPos) {
var indexTipDistance = Vec3.distance(this.leftIndexPos, this.rightIndexPos);
if (indexTipDistance < this.touchOnBelowDistance) {
this.walkingForward = true;
this.controlPoint = this.getControlPoint();
} else if (this.walkingForward && indexTipDistance > this.touchOffAboveDistance) {
this.walkingForward = false;
} // else don't change walkingForward
}
if (this.leftThumbPos && this.rightThumbPos) {
var thumbTipDistance = Vec3.distance(this.leftThumbPos, this.rightThumbPos);
if (thumbTipDistance < this.touchOnBelowDistance) {
this.walkingBackward = true;
this.controlPoint = this.getControlPoint();
} else if (this.walkingBackward && thumbTipDistance > this.touchOffAboveDistance) {
this.walkingBackward = false;
} // else don't change this.walkingBackward
}
if ((this.walkingForward || this.walkingBackward) && !this.mappingEnabled) {
Controller.enableMapping(this.mappingName);
this.mappingEnabled = true;
} else if (!(this.walkingForward || this.walkingBackward) && this.mappingEnabled) {
this.inputMapping.disable();
this.mappingEnabled = false;
} // else don't change mappingEnabled
};
this.leftIndexChanged = function (pose) {
if (pose.valid) {
this.leftIndexPos = pose.translation;
} else {
this.leftIndexPos = null;
}
this.updateWalking();
};
this.leftThumbChanged = function (pose) {
if (pose.valid) {
this.leftThumbPos = pose.translation;
} else {
this.leftThumbPos = null;
}
this.updateWalking();
};
this.rightIndexChanged = function (pose) {
if (pose.valid) {
this.rightIndexPos = pose.translation;
} else {
this.rightIndexPos = null;
}
this.updateWalking();
};
this.rightThumbChanged = function (pose) {
if (pose.valid) {
this.rightThumbPos = pose.translation;
} else {
this.rightThumbPos = null;
}
this.updateWalking();
};
this.isReady = function (controllerData) {
return makeRunningValues(handsAreTracked() && (this.walkingForward || this.walkingBackward), [], []);
};
this.run = function (controllerData) {
return this.isReady(controllerData);
};
this.setup = function () {
var _this = this;
this.inputGestureMapping.from(Controller.Standard.LeftHandIndex4).peek().to(function (pose) {
_this.leftIndexChanged(pose);
});
this.inputGestureMapping.from(Controller.Standard.LeftHandThumb4).peek().to(function (pose) {
_this.leftThumbChanged(pose);
});
this.inputGestureMapping.from(Controller.Standard.RightHandIndex4).peek().to(function (pose) {
_this.rightIndexChanged(pose);
});
this.inputGestureMapping.from(Controller.Standard.RightHandThumb4).peek().to(function (pose) {
_this.rightThumbChanged(pose);
});
this.inputMapping.from(function() {
if (_this.walkingForward) {
// var currentPoint = _this.getControlPoint();
// return currentPoint.z - _this.controlPoint.z;
return -0.5;
} else if (_this.walkingBackward) {
// var currentPoint = _this.getControlPoint();
// return currentPoint.z - _this.controlPoint.z;
return 0.5;
} else {
// return Controller.getActionValue(Controller.Standard.TranslateZ);
return 0.0;
}
}).to(Controller.Actions.TranslateZ);
// this.inputMapping.from(function() {
// if (_this.walkingForward) {
// var currentPoint = _this.getControlPoint();
// return currentPoint.x - _this.controlPoint.x;
// } else {
// return Controller.getActionValue(Controller.Standard.Yaw);
// }
// }).to(Controller.Actions.Yaw);
Controller.enableMapping(this.gestureMappingName);
};
this.cleanUp = function () {
this.inputGestureMapping.disable();
this.inputMapping.disable();
};
}
var trackedHandWalk = new TrackedHandWalk();
trackedHandWalk.setup();
enableDispatcherModule("TrackedHandWalk", trackedHandWalk);
function cleanup() {
trackedHandWalk.cleanUp();
disableDispatcherModule("TrackedHandWalk");
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -34,7 +34,9 @@ var CONTOLLER_SCRIPTS = [
"controllerModules/nearTabletHighlight.js",
"controllerModules/nearGrabEntity.js",
"controllerModules/farGrabEntity.js",
"controllerModules/pushToTalk.js"
"controllerModules/pushToTalk.js",
"controllerModules/trackedHandWalk.js",
"controllerModules/trackedHandTablet.js"
];
var DEBUG_MENU_ITEM = "Debug defaultScripts.js";

View file

@ -603,20 +603,28 @@ var toolBar = (function () {
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
}
// Make sure the model entity is loaded before we try to figure out
// its dimensions.
var MAX_LOADED_CHECKS = 10;
// its dimensions. We need to give ample time to load the entity.
var MAX_LOADED_CHECKS = 100; // 100 * 100ms = 10 seconds.
var LOADED_CHECK_INTERVAL = 100;
var isLoadedCheckCount = 0;
var entityIsLoadedCheck = function() {
isLoadedCheckCount++;
if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) {
var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions;
if (isLoadedCheckCount === MAX_LOADED_CHECKS) {
console.log("Model entity failed to load in time: " + (MAX_LOADED_CHECKS * LOADED_CHECK_INTERVAL) + " ... setting dimensions to: " + JSON.stringify(naturalDimensions))
}
Entities.editEntity(entityID, {
visible: true,
dimensions: naturalDimensions
})
dimensionsCheckCallback();
// We want to update the selection manager again since the script has moved on without us.
selectionManager.clearSelections(this);
entityListTool.sendUpdate();
selectionManager.setSelections([entityID], this);
return;
}
Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL);

View file

@ -0,0 +1,56 @@
/*
miniTablet.css
Copyright 2019 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
*/
* {
box-sizing: border-box;
padding: 0;
margin: 0;
user-select: none;
}
html {
background-color: #404040;
}
body {
height: 100%;
}
section {
background-color: #404040;
position: relative;
padding: 32px 0px;
}
.button {
text-align: center;
}
img {
width: 149px;
height: 149px;
}
#expand {
width: 149px;
height: 149px;
background-size: 100% 100%;
background-image: url("./img/mt-expand-normal.svg");
}
#expand:hover {
background-image: url("./img/mt-expand-hover.svg");
}
#expand:hover.unhover {
background-image: url("./img/mt-expand-normal.svg");
}
#expand img {
}

View file

@ -35,7 +35,9 @@
function setUnhover() {
if (!isUnhover) {
gotoButton.classList.add("unhover");
if (gotoButton) {
gotoButton.classList.add("unhover");
}
expandButton.classList.add("unhover");
isUnhover = true;
}
@ -43,7 +45,9 @@
function clearUnhover() {
if (isUnhover) {
gotoButton.classList.remove("unhover");
if (gotoButton) {
gotoButton.classList.remove("unhover");
}
expandButton.classList.remove("unhover");
isUnhover = false;
}
@ -62,10 +66,14 @@
switch (message.type) {
case MUTE_MESSAGE:
muteImage.src = message.icon;
if (muteImage) {
muteImage.src = message.icon;
}
break;
case GOTO_MESSAGE:
gotoImage.src = message.icon;
if (gotoImage) {
gotoImage.src = message.icon;
}
break;
}
}
@ -130,9 +138,7 @@
function onLoad() {
muteButton = document.getElementById("mute");
muteImage = document.getElementById("mute-img");
gotoButton = document.getElementById("goto");
gotoImage = document.getElementById("goto-img");
expandButton = document.getElementById("expand");
connectEventBridge();
@ -140,11 +146,19 @@
document.body.addEventListener("mouseenter", onBodyHover, false);
document.body.addEventListener("mouseleave", onBodyUnhover, false);
muteButton.addEventListener("mouseenter", onButtonHover, false);
gotoButton.addEventListener("mouseenter", onButtonHover, false);
if (muteButton) {
muteImage = document.getElementById("mute-img");
muteButton.addEventListener("mouseenter", onButtonHover, false);
muteButton.addEventListener("click", onMuteButtonClick, true);
}
if (gotoButton) {
gotoImage = document.getElementById("goto-img");
gotoButton.addEventListener("mouseenter", onButtonHover, false);
gotoButton.addEventListener("click", onGotoButtonClick, true);
}
expandButton.addEventListener("mouseenter", onButtonHover, false);
muteButton.addEventListener("click", onMuteButtonClick, true);
gotoButton.addEventListener("click", onGotoButtonClick, true);
expandButton.addEventListener("click", onExpandButtonClick, true);
document.body.onunload = function () {

View file

@ -0,0 +1,26 @@
<!--
miniTablet.html
Created by David Rowe on 20 Aug 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
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="css/miniHandTablet.css" />
</head>
<body>
<section>
<div id="expand" class="button">
<img src="./img/expand.svg" />
</div>
</section>
<script src="js/miniTablet.js"></script>
</body>
</html>

View file

@ -164,6 +164,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) {
parentID: this.tabletEntityID,
parentJointIndex: -1,
showKeyboardFocusHighlight: false,
grabbable: false,
visible: visible
});

View file

@ -6,7 +6,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global module, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4,
Selection, Uuid,
Selection, Uuid, Controller,
MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true,
HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true,
@ -56,6 +56,7 @@
TEAR_AWAY_DISTANCE:true,
TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME:true,
TELEPORT_DEADZONE: true,
NEAR_GRAB_DISTANCE: true,
distanceBetweenPointAndEntityBoundingBox:true,
entityIsEquipped:true,
@ -63,7 +64,8 @@
clearHighlightedEntities:true,
unhighlightTargetEntity:true,
distanceBetweenEntityLocalPositionAndBoundingBox: true,
worldPositionToRegistrationFrameMatrix: true
worldPositionToRegistrationFrameMatrix: true,
handsAreTracked: true
*/
MSECS_PER_SEC = 1000.0;
@ -600,6 +602,10 @@ worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) {
return offsetMat;
};
handsAreTracked = function () {
return Controller.getPoseValue(Controller.Standard.LeftHandIndex3).valid ||
Controller.getPoseValue(Controller.Standard.RightHandIndex3).valid;
};
if (typeof module !== 'undefined') {
module.exports = {
@ -624,6 +630,7 @@ if (typeof module !== 'undefined') {
TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE,
TRIGGER_ON_VALUE: TRIGGER_ON_VALUE,
DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST,
worldPositionToRegistrationFrameMatrix: worldPositionToRegistrationFrameMatrix
worldPositionToRegistrationFrameMatrix: worldPositionToRegistrationFrameMatrix,
handsAreTracked: handsAreTracked
};
}

View file

@ -8,7 +8,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global getTabletWidthFromSettings, TRIGGER_OFF_VALUE */
/* global getTabletWidthFromSettings, handsAreTracked, TRIGGER_OFF_VALUE, Controller, Script, Camera, Tablet, MyAvatar,
Quat, SoundCache, HMD, Overlays, Vec3, Uuid, Messages */
(function () {
@ -80,7 +81,6 @@
return hand === LEFT_HAND ? RIGHT_HAND : LEFT_HAND;
}
UI = function () {
if (!(this instanceof UI)) {
@ -114,6 +114,7 @@
uiHand = LEFT_HAND,
miniUIOverlay = null,
MINI_UI_HTML = Script.resolvePath("./html/miniTablet.html"),
MINI_HAND_UI_HTML = Script.resolvePath("./html/miniHandsTablet.html"),
MINI_UI_DIMENSIONS = { x: 0.059, y: 0.0865, z: 0.001 },
MINI_UI_WIDTH_PIXELS = 150,
METERS_TO_INCHES = 39.3701,
@ -291,6 +292,7 @@
visible: true
});
Overlays.editOverlay(miniUIOverlay, {
url: handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML,
localPosition: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION),
localRotation: MINI_UI_LOCAL_ROTATION,
dimensions: Vec3.multiply(initialScale, MINI_UI_DIMENSIONS),
@ -353,8 +355,8 @@
localRotation,
localPosition;
tabletScaleFactor = MyAvatar.sensorToWorldScale
* (1 + scaleFactor * (miniTargetWidth - miniInitialWidth) / miniInitialWidth);
tabletScaleFactor = MyAvatar.sensorToWorldScale *
(1 + scaleFactor * (miniTargetWidth - miniInitialWidth) / miniInitialWidth);
dimensions = Vec3.multiply(tabletScaleFactor, MINI_DIMENSIONS);
localRotation = Quat.mix(miniExpandLocalRotation, miniTargetLocalRotation, scaleFactor);
localPosition =
@ -469,11 +471,11 @@
solid: true,
grabbable: true,
showKeyboardFocusHighlight: false,
drawInFront: true,
drawInFront: false,
visible: false
});
miniUIOverlay = Overlays.addOverlay("web3d", {
url: MINI_UI_HTML,
url: handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML,
parentID: miniOverlay,
localPosition: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION),
localRotation: MINI_UI_LOCAL_ROTATION,
@ -482,7 +484,7 @@
alpha: 0, // Hide overlay while its content is being created.
grabbable: false,
showKeyboardFocusHighlight: false,
drawInFront: true,
drawInFront: false,
visible: false
});
@ -642,8 +644,8 @@
// is grabbing something) or the other hand's trigger is pressed unless it is pointing at the mini tablet. Allow
// the triggers to be pressed briefly to allow for the grabbing process.
if (show) {
isLeftTriggerOff = Controller.getValue(Controller.Standard.LT) < TRIGGER_OFF_VALUE
&& Controller.getValue(Controller.Standard.LeftGrip) < TRIGGER_OFF_VALUE;
isLeftTriggerOff = Controller.getValue(Controller.Standard.LT) < TRIGGER_OFF_VALUE &&
Controller.getValue(Controller.Standard.LeftGrip) < TRIGGER_OFF_VALUE;
if (!isLeftTriggerOff) {
if (leftTriggerOn === 0) {
leftTriggerOn = Date.now();
@ -653,8 +655,8 @@
} else {
leftTriggerOn = 0;
}
isRightTriggerOff = Controller.getValue(Controller.Standard.RT) < TRIGGER_OFF_VALUE
&& Controller.getValue(Controller.Standard.RightGrip) < TRIGGER_OFF_VALUE;
isRightTriggerOff = Controller.getValue(Controller.Standard.RT) < TRIGGER_OFF_VALUE &&
Controller.getValue(Controller.Standard.RightGrip) < TRIGGER_OFF_VALUE;
if (!isRightTriggerOff) {
if (rightTriggerOn === 0) {
rightTriggerOn = Date.now();
@ -665,8 +667,8 @@
rightTriggerOn = 0;
}
show = (hand === LEFT_HAND ? wasLeftTriggerOff : wasRightTriggerOff)
&& ((hand === LEFT_HAND ? wasRightTriggerOff : wasLeftTriggerOff) || ui.isLaserPointingAt());
show = (hand === LEFT_HAND ? wasLeftTriggerOff : wasRightTriggerOff) &&
((hand === LEFT_HAND ? wasRightTriggerOff : wasLeftTriggerOff) || ui.isLaserPointingAt());
}
// Should show mini tablet if it would be oriented toward the camera.
@ -691,10 +693,10 @@
normalDot = Vec3.dot(normalHandVector, miniToCameraDirection);
medialAngle = Math.atan2(medialDot, normalDot);
lateralAngle = Math.atan2(lateralDot, normalDot);
show = -MAX_MEDIAL_WRIST_CAMERA_ANGLE_RAD <= medialAngle
&& medialAngle <= MAX_MEDIAL_FINGER_CAMERA_ANGLE_RAD
&& -MAX_LATERAL_THUMB_CAMERA_ANGLE_RAD <= lateralAngle
&& lateralAngle <= MAX_LATERAL_PINKY_CAMERA_ANGLE_RAD;
show = -MAX_MEDIAL_WRIST_CAMERA_ANGLE_RAD <= medialAngle &&
medialAngle <= MAX_MEDIAL_FINGER_CAMERA_ANGLE_RAD &&
-MAX_LATERAL_THUMB_CAMERA_ANGLE_RAD <= lateralAngle &&
lateralAngle <= MAX_LATERAL_PINKY_CAMERA_ANGLE_RAD;
// Camera looking at mini tablet?
cameraToMini = -Vec3.dot(miniToCameraDirection, Quat.getForward(Camera.orientation));
@ -972,8 +974,8 @@
function setState(state, data) {
if (state !== miniState) {
debug("State transition from " + STATE_STRINGS[miniState] + " to " + STATE_STRINGS[state]
+ ( data ? " " + JSON.stringify(data) : ""));
debug("State transition from " + STATE_STRINGS[miniState] + " to " + STATE_STRINGS[state] +
( data ? " " + JSON.stringify(data) : ""));
if (STATE_MACHINE[STATE_STRINGS[miniState]].exit) {
STATE_MACHINE[STATE_STRINGS[miniState]].exit(data);
}
@ -1061,8 +1063,8 @@
return;
}
if (miniState.getState() === miniState.MINI_DISABLED
|| (message.grabbedEntity !== HMD.tabletID && message.grabbedEntity !== ui.getMiniTabletID())) {
if (miniState.getState() === miniState.MINI_DISABLED ||
(message.grabbedEntity !== HMD.tabletID && message.grabbedEntity !== ui.getMiniTabletID())) {
return;
}

View file

@ -6,7 +6,7 @@
// Created by Keb Helion, February 2020.
// Copyright 2020 Vircadia contributors.
//
// This script adds a "More Apps" selector to "Project Athena" to allow the user to add optional functionalities to the tablet.
// This script adds a "More Apps" selector to Vircadia to allow the user to add optional functionalities to the tablet.
// This application has been designed to work directly from the Github repository.
//
// Distributed under the Apache License, Version 2.0.

View file

@ -42,6 +42,10 @@ p a {
color: SteelBlue;
}
.appdesc a {
color: LightBlue;
}
font.appname {
font-family: 'Merriweather', sans-serif;
font-size: 18px;