mirror of
https://github.com/lubosz/overte.git
synced 2025-04-13 20:06:16 +02:00
merging and updating
This commit is contained in:
commit
2ed7aae8d5
825 changed files with 53718 additions and 23691 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -4,6 +4,7 @@ CMakeFiles/
|
|||
CMakeScripts/
|
||||
cmake_install.cmake
|
||||
build*/
|
||||
ext/
|
||||
Makefile
|
||||
*.user
|
||||
|
||||
|
@ -32,10 +33,6 @@ DerivedData
|
|||
interface/external/*/*
|
||||
!interface/external/*/readme.txt
|
||||
|
||||
# ignore interface optional resources
|
||||
interface/resources/visage/*
|
||||
!interface/resources/visage/tracker.cfg
|
||||
|
||||
# Ignore interfaceCache for Linux users
|
||||
interface/interfaceCache/
|
||||
|
||||
|
@ -46,4 +43,6 @@ libraries/audio-client/external/*/*
|
|||
gvr-interface/assets/oculussig*
|
||||
gvr-interface/libs/*
|
||||
|
||||
TAGS
|
||||
# ignore files for various dev environments
|
||||
TAGS
|
||||
*.swp
|
||||
|
|
18
BUILD.md
18
BUILD.md
|
@ -1,9 +1,9 @@
|
|||
###Dependencies
|
||||
|
||||
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.12.2
|
||||
* [Qt](http://qt-project.org/downloads) ~> 5.3.2
|
||||
* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g
|
||||
* IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability.
|
||||
* [Qt](http://www.qt.io/download-open-source) ~> 5.4.1
|
||||
* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1m
|
||||
* IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities.
|
||||
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
|
||||
####CMake External Project Dependencies
|
||||
|
@ -19,9 +19,9 @@ The following external projects are optional dependencies. You can indicate to C
|
|||
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
|
||||
* Enables game controller support in Interface
|
||||
|
||||
The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build-ext` directory in each of the subfolders for each external project.
|
||||
The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build/ext` folder in each of the subfolders for each external project.
|
||||
|
||||
These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build-ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build-ext` folder.
|
||||
These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build/ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build/ext` folder.
|
||||
|
||||
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DGET_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
|
||||
|
||||
|
@ -37,12 +37,12 @@ Hifi uses CMake to generate build files and project files for your platform.
|
|||
####Qt
|
||||
In order for CMake to find the Qt5 find modules, you will need to set an ENV variable pointing to your Qt installation.
|
||||
|
||||
For example, a Qt5 5.3.2 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
|
||||
For example, a Qt5 5.4.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
|
||||
|
||||
The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.3.2/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.3.2/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.4.1/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.4.1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
|
||||
|
||||
####Generating build files
|
||||
|
@ -57,7 +57,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E
|
|||
|
||||
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
|
||||
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.3.2/lib/cmake
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.4.1/lib/cmake
|
||||
|
||||
####Finding Dependencies
|
||||
|
||||
|
|
|
@ -3,4 +3,4 @@ Please read the [general build guide](BUILD.md) for information on dependencies
|
|||
###Qt5 Dependencies
|
||||
Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required:
|
||||
|
||||
libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
|
||||
libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev libxrandr-dev
|
||||
|
|
|
@ -10,7 +10,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies
|
|||
|
||||
We have a [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas) that you can use/tap to install some of the dependencies. In the code block above qt5 is installed from a formula in this repository.
|
||||
|
||||
*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.3.x stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.*
|
||||
*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.4.x stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.*
|
||||
|
||||
###Xcode
|
||||
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
|
||||
|
|
|
@ -29,12 +29,12 @@ NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit
|
|||
|
||||
* [Download the online installer](http://qt-project.org/downloads)
|
||||
* When it asks you to select components, ONLY select the following:
|
||||
* Qt > Qt 5.3.2 > **msvc2013 32-bit OpenGL**
|
||||
* Qt > Qt 5.4.1 > **msvc2013 32-bit OpenGL**
|
||||
|
||||
* [Download the offline installer](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe)
|
||||
* [Download the offline installer](http://download.qt.io/official_releases/qt/5.4/5.4.1/qt-opensource-windows-x86-msvc2013_opengl-5.4.1.exe)
|
||||
|
||||
Once Qt is installed, you need to manually configure the following:
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.3.2\msvc2013_opengl\lib\cmake` directory.
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.4.1\msvc2013_opengl\lib\cmake` directory.
|
||||
* You can set an environment variable from Control Panel > System > Advanced System Settings > Environment Variables > New
|
||||
|
||||
###External Libraries
|
||||
|
@ -71,7 +71,7 @@ Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll
|
|||
|
||||
To prevent these problems, install OpenSSL yourself. Download the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html):
|
||||
* Visual C++ 2008 Redistributables
|
||||
* Win32 OpenSSL v1.0.1L
|
||||
* Win32 OpenSSL v1.0.1m
|
||||
|
||||
Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version.
|
||||
|
||||
|
|
|
@ -21,8 +21,12 @@ if (POLICY CMP0042)
|
|||
cmake_policy(SET CMP0042 OLD)
|
||||
endif ()
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets")
|
||||
|
||||
project(hifi)
|
||||
add_definitions(-DGLM_FORCE_RADIANS)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
||||
|
||||
if (WIN32)
|
||||
add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)
|
||||
|
@ -37,10 +41,15 @@ if (WIN32)
|
|||
message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH})
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH})
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
|
||||
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing -ggdb")
|
||||
# /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory.
|
||||
# Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables
|
||||
# TODO: Remove when building 64-bit.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter")
|
||||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
|
||||
endif ()
|
||||
endif(WIN32)
|
||||
|
||||
if (NOT MSVC12)
|
||||
|
@ -84,8 +93,17 @@ else ()
|
|||
if (NOT QT_CMAKE_PREFIX_PATH)
|
||||
set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
|
||||
endif ()
|
||||
if (NOT QT_CMAKE_PREFIX_PATH)
|
||||
get_filename_component(QT_CMAKE_PREFIX_PATH "${Qt5_DIR}/.." REALPATH)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
if (NOT EXISTS ${QT_CMAKE_PREFIX_PATH})
|
||||
message(FATAL_ERROR "Could not determine QT_CMAKE_PREFIX_PATH.")
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# figure out where the qt dir is
|
||||
get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE)
|
||||
|
||||
|
@ -158,6 +176,9 @@ option(GET_GLM "Get GLM library automatically as external project" 1)
|
|||
option(GET_GVERB "Get Gverb library automatically as external project" 1)
|
||||
option(GET_SOXR "Get Soxr library automatically as external project" 1)
|
||||
option(GET_TBB "Get Threading Building Blocks library automatically as external project" 1)
|
||||
option(GET_LIBOVR "Get LibOVR library automatically as external project" 1)
|
||||
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
|
||||
option(GET_VHACD "Get V-HACD library automatically as external project" 1)
|
||||
|
||||
if (WIN32)
|
||||
option(GET_GLEW "Get GLEW library automatically as external project" 1)
|
||||
|
@ -172,13 +193,17 @@ endif ()
|
|||
# add subdirectories for all targets
|
||||
if (NOT ANDROID)
|
||||
add_subdirectory(assignment-client)
|
||||
set_target_properties(assignment-client PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(domain-server)
|
||||
set_target_properties(domain-server PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(ice-server)
|
||||
set_target_properties(ice-server PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(interface)
|
||||
set_target_properties(interface PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(tools)
|
||||
endif ()
|
||||
|
||||
if (ANDROID OR DESKTOP_GVR)
|
||||
add_subdirectory(gvr-interface)
|
||||
endif ()
|
||||
endif ()
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkDiskCache>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
@ -46,7 +45,7 @@ Agent::Agent(const QByteArray& packet) :
|
|||
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
DependencyManager::set<ResouceCacheSharedItems>();
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<SoundCache>();
|
||||
}
|
||||
|
||||
|
@ -225,5 +224,7 @@ void Agent::run() {
|
|||
|
||||
void Agent::aboutToFinish() {
|
||||
_scriptEngine.stop();
|
||||
NetworkAccessManager::getInstance().clearAccessCache();
|
||||
|
||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QSharedMemory>
|
||||
|
@ -18,6 +20,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <AvatarHashMap.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <LogHandler.h>
|
||||
#include <LogUtils.h>
|
||||
|
@ -29,31 +32,31 @@
|
|||
#include <SoundCache.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentThread.h"
|
||||
|
||||
#include "AssignmentClient.h"
|
||||
|
||||
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
|
||||
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
|
||||
|
||||
SharedAssignmentPointer AssignmentClient::_currentAssignment;
|
||||
|
||||
int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");
|
||||
|
||||
AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool, QUuid walletUUID,
|
||||
QString assignmentServerHostname, quint16 assignmentServerPort) :
|
||||
AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
|
||||
quint16 assignmentMonitorPort) :
|
||||
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME),
|
||||
_localASPortSharedMem(NULL),
|
||||
_localACMPortSharedMem(NULL)
|
||||
_localASPortSharedMem(NULL)
|
||||
{
|
||||
LogUtils::init();
|
||||
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
|
||||
// create a NodeList as an unassigned client
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned);
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned); // Order is important
|
||||
|
||||
auto animationCache = DependencyManager::set<AnimationCache>();
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
|
||||
|
||||
// make up a uuid for this child so the parent can tell us apart. This id will be changed
|
||||
|
@ -73,18 +76,18 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
|
||||
_requestAssignment.setWalletUUID(walletUUID);
|
||||
}
|
||||
|
||||
|
||||
// check for an overriden assignment server hostname
|
||||
if (assignmentServerHostname != "") {
|
||||
// change the hostname for our assignment server
|
||||
_assignmentServerHostname = assignmentServerHostname;
|
||||
}
|
||||
|
||||
|
||||
_assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
|
||||
qDebug() << "Assignment server socket is" << _assignmentServerSocket;
|
||||
|
||||
|
||||
// call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required
|
||||
qDebug() << "Waiting for assignment -" << _requestAssignment;
|
||||
|
||||
|
@ -101,32 +104,57 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
// connections to AccountManager for authentication
|
||||
connect(&AccountManager::getInstance(), &AccountManager::authRequired,
|
||||
this, &AssignmentClient::handleAuthenticationRequest);
|
||||
|
||||
|
||||
// Create Singleton objects on main thread
|
||||
NetworkAccessManager::getInstance();
|
||||
|
||||
// Hook up a timer to send this child's status to the Monitor once per second
|
||||
setUpStatsToMonitor();
|
||||
// did we get an assignment-client monitor port?
|
||||
if (assignmentMonitorPort > 0) {
|
||||
_assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort);
|
||||
|
||||
qDebug() << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
|
||||
|
||||
// Hook up a timer to send this child's status to the Monitor once per second
|
||||
setUpStatsToMonitor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AssignmentClient::stopAssignmentClient() {
|
||||
qDebug() << "Exiting.";
|
||||
qDebug() << "Forced stop of assignment-client.";
|
||||
|
||||
_requestTimer.stop();
|
||||
_statsTimerACM.stop();
|
||||
QCoreApplication::quit();
|
||||
|
||||
if (_currentAssignment) {
|
||||
// grab the thread for the current assignment
|
||||
QThread* currentAssignmentThread = _currentAssignment->thread();
|
||||
|
||||
// ask the current assignment to stop
|
||||
QMetaObject::invokeMethod(_currentAssignment, "stop", Qt::BlockingQueuedConnection);
|
||||
|
||||
// ask the current assignment to delete itself on its thread
|
||||
_currentAssignment->deleteLater();
|
||||
|
||||
// when this thread is destroyed we don't need to run our assignment complete method
|
||||
disconnect(currentAssignmentThread, &QThread::destroyed, this, &AssignmentClient::assignmentCompleted);
|
||||
|
||||
// wait on the thread from that assignment - it will be gone once the current assignment deletes
|
||||
currentAssignmentThread->quit();
|
||||
currentAssignmentThread->wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AssignmentClient::aboutToQuit() {
|
||||
stopAssignmentClient();
|
||||
|
||||
// clear the log handler so that Qt doesn't call the destructor on LogHandler
|
||||
qInstallMessageHandler(0);
|
||||
}
|
||||
|
||||
|
||||
void AssignmentClient::setUpStatsToMonitor() {
|
||||
// Figure out the address to send out stats to
|
||||
quint16 localMonitorServerPort = DEFAULT_ASSIGNMENT_CLIENT_MONITOR_PORT;
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->getLocalServerPortFromSharedMemory(ASSIGNMENT_CLIENT_MONITOR_LOCAL_PORT_SMEM_KEY,
|
||||
_localACMPortSharedMem, localMonitorServerPort);
|
||||
_assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, localMonitorServerPort, true);
|
||||
|
||||
// send a stats packet every 1 seconds
|
||||
connect(&_statsTimerACM, &QTimer::timeout, this, &AssignmentClient::sendStatsPacketToACM);
|
||||
_statsTimerACM.start(1000);
|
||||
|
@ -147,9 +175,9 @@ void AssignmentClient::sendStatsPacketToACM() {
|
|||
|
||||
void AssignmentClient::sendAssignmentRequest() {
|
||||
if (!_currentAssignment) {
|
||||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
||||
if (_assignmentServerHostname == "localhost") {
|
||||
// we want to check again for the local domain-server port in case the DS has restarted
|
||||
quint16 localAssignmentServerPort;
|
||||
|
@ -158,13 +186,13 @@ void AssignmentClient::sendAssignmentRequest() {
|
|||
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
|
||||
qDebug() << "Port for local assignment server read from shared memory is"
|
||||
<< localAssignmentServerPort;
|
||||
|
||||
|
||||
_assignmentServerSocket.setPort(localAssignmentServerPort);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nodeList->sendAssignment(_requestAssignment);
|
||||
}
|
||||
}
|
||||
|
@ -182,8 +210,11 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
|
||||
|
||||
qDebug() << "Received a PacketTypeCreateAssignment - attempting to unpack.";
|
||||
|
||||
// construct the deployed assignment from the packet data
|
||||
_currentAssignment = SharedAssignmentPointer(AssignmentFactory::unpackAssignment(receivedPacket));
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
|
||||
|
||||
if (_currentAssignment) {
|
||||
qDebug() << "Received an assignment -" << *_currentAssignment;
|
||||
|
@ -196,14 +227,26 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
|
||||
|
||||
// start the deployed assignment
|
||||
AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this);
|
||||
QThread* workerThread = new QThread;
|
||||
workerThread->setObjectName("ThreadedAssignment Worker");
|
||||
|
||||
connect(workerThread, &QThread::started, _currentAssignment.data(), &ThreadedAssignment::run);
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished, workerThread, &QThread::quit);
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished,
|
||||
this, &AssignmentClient::assignmentCompleted);
|
||||
|
||||
// Once the ThreadedAssignment says it is finished - we ask it to deleteLater
|
||||
// This is a queued connection so that it is put into the event loop to be processed by the worker
|
||||
// thread when it is ready.
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished, _currentAssignment.data(),
|
||||
&ThreadedAssignment::deleteLater, Qt::QueuedConnection);
|
||||
|
||||
// once it is deleted, we quit the worker thread
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::destroyed, workerThread, &QThread::quit);
|
||||
|
||||
// have the worker thread remove itself once it is done
|
||||
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
||||
|
||||
// once the worker thread says it is done, we consider the assignment completed
|
||||
connect(workerThread, &QThread::destroyed, this, &AssignmentClient::assignmentCompleted);
|
||||
|
||||
_currentAssignment->moveToThread(workerThread);
|
||||
|
||||
// move the NodeList to the thread used for the _current assignment
|
||||
|
@ -222,8 +265,9 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
} else if (packetTypeForPacket(receivedPacket) == PacketTypeStopNode) {
|
||||
if (senderSockAddr.getAddress() == QHostAddress::LocalHost ||
|
||||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
|
||||
qDebug() << "Network told me to exit.";
|
||||
emit stopAssignmentClient();
|
||||
qDebug() << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketTypeStopNode.";
|
||||
|
||||
QCoreApplication::quit();
|
||||
} else {
|
||||
qDebug() << "Got a stop packet from other than localhost.";
|
||||
}
|
||||
|
@ -260,21 +304,23 @@ void AssignmentClient::handleAuthenticationRequest() {
|
|||
}
|
||||
|
||||
void AssignmentClient::assignmentCompleted() {
|
||||
|
||||
// we expect that to be here the previous assignment has completely cleaned up
|
||||
assert(_currentAssignment.isNull());
|
||||
|
||||
// reset our current assignment pointer to NULL now that it has been deleted
|
||||
_currentAssignment = NULL;
|
||||
|
||||
// reset the logging target to the the CHILD_TARGET_NAME
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
|
||||
|
||||
qDebug("Assignment finished or never started - waiting for new assignment.");
|
||||
qDebug() << "Assignment finished or never started - waiting for new assignment.";
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// have us handle incoming NodeList datagrams again
|
||||
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment.data(), 0);
|
||||
// have us handle incoming NodeList datagrams again, and make sure our ThreadedAssignment isn't handling them
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
|
||||
|
||||
// clear our current assignment shared pointer now that we're done with it
|
||||
// if the assignment thread is still around it has its own shared pointer to the assignment
|
||||
_currentAssignment.clear();
|
||||
|
||||
// reset our NodeList by switching back to unassigned and clearing the list
|
||||
nodeList->setOwnerType(NodeType::Unassigned);
|
||||
nodeList->reset();
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_AssignmentClient_h
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
#include "ThreadedAssignment.h"
|
||||
|
||||
|
@ -23,9 +24,8 @@ class AssignmentClient : public QObject {
|
|||
public:
|
||||
|
||||
AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort);
|
||||
static const SharedAssignmentPointer& getCurrentAssignment() { return _currentAssignment; }
|
||||
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
|
||||
quint16 assignmentMonitorPort);
|
||||
private slots:
|
||||
void sendAssignmentRequest();
|
||||
void readPendingDatagrams();
|
||||
|
@ -34,14 +34,17 @@ private slots:
|
|||
void sendStatsPacketToACM();
|
||||
void stopAssignmentClient();
|
||||
|
||||
public slots:
|
||||
void aboutToQuit();
|
||||
|
||||
private:
|
||||
void setUpStatsToMonitor();
|
||||
|
||||
Assignment _requestAssignment;
|
||||
static SharedAssignmentPointer _currentAssignment;
|
||||
QPointer<ThreadedAssignment> _currentAssignment;
|
||||
QString _assignmentServerHostname;
|
||||
HifiSockAddr _assignmentServerSocket;
|
||||
QSharedMemory* _localASPortSharedMem; // memory shared with domain server
|
||||
QSharedMemory* _localACMPortSharedMem; // memory shared with assignment client monitor
|
||||
QTimer _requestTimer; // timer for requesting and assignment
|
||||
QTimer _statsTimerACM; // timer for sending stats to assignment client monitor
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QThread>
|
||||
|
||||
#include <LogHandler.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -78,6 +79,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
const QCommandLineOption maxChildsOption(ASSIGNMENT_MAX_FORKS_OPTION, "maximum number of children", "child-count");
|
||||
parser.addOption(maxChildsOption);
|
||||
|
||||
const QCommandLineOption monitorPortOption(ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION, "assignment-client monitor port", "port");
|
||||
parser.addOption(monitorPortOption);
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments())) {
|
||||
qCritical() << parser.errorText() << endl;
|
||||
|
@ -109,6 +112,11 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
maxForks = parser.value(maxChildsOption).toInt();
|
||||
}
|
||||
|
||||
unsigned short monitorPort = 0;
|
||||
if (parser.isSet(monitorPortOption)) {
|
||||
monitorPort = parser.value(monitorPortOption).toUShort();
|
||||
}
|
||||
|
||||
if (!numForks && minForks) {
|
||||
// if the user specified --min but not -n, set -n to --min
|
||||
numForks = minForks;
|
||||
|
@ -153,12 +161,11 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
if (argumentVariantMap.contains(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) {
|
||||
assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt();
|
||||
}
|
||||
|
||||
if (parser.isSet(assignmentServerPortOption)) {
|
||||
assignmentServerPort = parser.value(assignmentServerPortOption).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (parser.isSet(numChildsOption)) {
|
||||
if (minForks && minForks > numForks) {
|
||||
qCritical() << "--min can't be more than -n";
|
||||
|
@ -172,14 +179,22 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
}
|
||||
}
|
||||
|
||||
QThread::currentThread()->setObjectName("main thread");
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
|
||||
if (numForks || minForks || maxForks) {
|
||||
AssignmentClientMonitor monitor(numForks, minForks, maxForks, assignmentPool,
|
||||
walletUUID, assignmentServerHostname, assignmentServerPort);
|
||||
exec();
|
||||
AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks,
|
||||
requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname,
|
||||
assignmentServerPort);
|
||||
monitor->setParent(this);
|
||||
connect(this, &QCoreApplication::aboutToQuit, monitor, &AssignmentClientMonitor::aboutToQuit);
|
||||
} else {
|
||||
AssignmentClient client(requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname, assignmentServerPort);
|
||||
exec();
|
||||
AssignmentClient* client = new AssignmentClient(requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname,
|
||||
assignmentServerPort, monitorPort);
|
||||
client->setParent(this);
|
||||
connect(this, &QCoreApplication::aboutToQuit, client, &AssignmentClient::aboutToQuit);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ const QString CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION = "p";
|
|||
const QString ASSIGNMENT_NUM_FORKS_OPTION = "n";
|
||||
const QString ASSIGNMENT_MIN_FORKS_OPTION = "min";
|
||||
const QString ASSIGNMENT_MAX_FORKS_OPTION = "max";
|
||||
const QString ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION = "monitor-port";
|
||||
|
||||
|
||||
class AssignmentClientApp : public QCoreApplication {
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
|
||||
#include <signal.h>
|
||||
|
||||
#include <LogHandler.h>
|
||||
#include <AddressManager.h>
|
||||
#include <JSONBreakableMarshal.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "AssignmentClientMonitor.h"
|
||||
#include "AssignmentClientApp.h"
|
||||
|
@ -20,36 +21,40 @@
|
|||
#include "PacketHeaders.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
const char* NUM_FORKS_PARAMETER = "-n";
|
||||
|
||||
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
|
||||
const int WAIT_FOR_CHILD_MSECS = 1000;
|
||||
|
||||
AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks,
|
||||
const unsigned int minAssignmentClientForks,
|
||||
const unsigned int maxAssignmentClientForks,
|
||||
QString assignmentPool, QUuid walletUUID, QString assignmentServerHostname,
|
||||
Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort) :
|
||||
_numAssignmentClientForks(numAssignmentClientForks),
|
||||
_minAssignmentClientForks(minAssignmentClientForks),
|
||||
_maxAssignmentClientForks(maxAssignmentClientForks),
|
||||
_requestAssignmentType(requestAssignmentType),
|
||||
_assignmentPool(assignmentPool),
|
||||
_walletUUID(walletUUID),
|
||||
_assignmentServerHostname(assignmentServerHostname),
|
||||
_assignmentServerPort(assignmentServerPort)
|
||||
{
|
||||
qDebug() << "_requestAssignmentType =" << _requestAssignmentType;
|
||||
|
||||
// start the Logging class with the parent's target name
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME);
|
||||
|
||||
// make sure we output process IDs for a monitor otherwise it's insane to parse
|
||||
LogHandler::getInstance().setShouldOutputPID(true);
|
||||
|
||||
// create a NodeList so we can receive stats from children
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<LimitedNodeList>(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_PORT);
|
||||
auto nodeList = DependencyManager::set<LimitedNodeList>();
|
||||
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClientMonitor::readPendingDatagrams);
|
||||
|
||||
nodeList->putLocalPortIntoSharedMemory(ASSIGNMENT_CLIENT_MONITOR_LOCAL_PORT_SMEM_KEY, this,
|
||||
nodeList->getNodeSocket().localPort());
|
||||
|
||||
// use QProcess to fork off a process for each of the child assignment clients
|
||||
for (unsigned int i = 0; i < _numAssignmentClientForks; i++) {
|
||||
spawnChildClient();
|
||||
|
@ -64,20 +69,60 @@ AssignmentClientMonitor::~AssignmentClientMonitor() {
|
|||
stopChildProcesses();
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) {
|
||||
QElapsedTimer waitTimer;
|
||||
waitTimer.start();
|
||||
|
||||
// loop as long as we still have processes around and we're inside the wait window
|
||||
while(_childProcesses.size() > 0 && !waitTimer.hasExpired(waitMsecs)) {
|
||||
// continue processing events so we can handle a process finishing up
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::childProcessFinished() {
|
||||
QProcess* childProcess = qobject_cast<QProcess*>(sender());
|
||||
qint64 processID = _childProcesses.key(childProcess);
|
||||
|
||||
if (processID > 0) {
|
||||
qDebug() << "Child process" << processID << "has finished. Removing from internal map.";
|
||||
_childProcesses.remove(processID);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::stopChildProcesses() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
qDebug() << "asking child" << node->getUUID() << "to exit.";
|
||||
node->activateLocalSocket();
|
||||
QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
nodeList->writeUnverifiedDatagram(diePacket, *node->getActiveSocket());
|
||||
});
|
||||
// ask child processes to terminate
|
||||
foreach(QProcess* childProcess, _childProcesses) {
|
||||
qDebug() << "Attempting to terminate child process" << childProcess->processId();
|
||||
childProcess->terminate();
|
||||
}
|
||||
|
||||
simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS);
|
||||
|
||||
if (_childProcesses.size() > 0) {
|
||||
// ask even more firmly
|
||||
foreach(QProcess* childProcess, _childProcesses) {
|
||||
qDebug() << "Attempting to kill child process" << childProcess->processId();
|
||||
childProcess->kill();
|
||||
}
|
||||
|
||||
simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::aboutToQuit() {
|
||||
stopChildProcesses();
|
||||
|
||||
// clear the log handler so that Qt doesn't call the destructor on LogHandler
|
||||
qInstallMessageHandler(0);
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::spawnChildClient() {
|
||||
QProcess *assignmentClient = new QProcess(this);
|
||||
QProcess* assignmentClient = new QProcess(this);
|
||||
|
||||
|
||||
// unparse the parts of the command-line that the child cares about
|
||||
QStringList _childArguments;
|
||||
if (_assignmentPool != "") {
|
||||
|
@ -96,17 +141,28 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
_childArguments.append("--" + CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION);
|
||||
_childArguments.append(QString::number(_assignmentServerPort));
|
||||
}
|
||||
if (_requestAssignmentType != Assignment::AllTypes) {
|
||||
_childArguments.append("--" + ASSIGNMENT_TYPE_OVERRIDE_OPTION);
|
||||
_childArguments.append(QString::number(_requestAssignmentType));
|
||||
}
|
||||
|
||||
// tell children which assignment monitor port to use
|
||||
// for now they simply talk to us on localhost
|
||||
_childArguments.append("--" + ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION);
|
||||
_childArguments.append(QString::number(DependencyManager::get<NodeList>()->getLocalSockAddr().getPort()));
|
||||
|
||||
// make sure that the output from the child process appears in our output
|
||||
assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
|
||||
assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments);
|
||||
|
||||
// make sure we hear that this process has finished when it does
|
||||
connect(assignmentClient, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(childProcessFinished()));
|
||||
|
||||
qDebug() << "Spawned a child client with PID" << assignmentClient->pid();
|
||||
_childProcesses.insert(assignmentClient->processId(), assignmentClient);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AssignmentClientMonitor::checkSpares() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QUuid aSpareId = "";
|
||||
|
@ -138,7 +194,8 @@ void AssignmentClientMonitor::checkSpares() {
|
|||
qDebug() << "asking child" << aSpareId << "to exit.";
|
||||
SharedNodePointer childNode = nodeList->nodeWithUUID(aSpareId);
|
||||
childNode->activateLocalSocket();
|
||||
QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
|
||||
QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
nodeList->writeUnverifiedDatagram(diePacket, childNode);
|
||||
}
|
||||
}
|
||||
|
@ -166,13 +223,13 @@ void AssignmentClientMonitor::readPendingDatagrams() {
|
|||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
|
||||
if (!packetUUID.isNull()) {
|
||||
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode
|
||||
(packetUUID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false);
|
||||
(packetUUID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false);
|
||||
AssignmentClientChildData *childData = new AssignmentClientChildData("unknown");
|
||||
matchingNode->setLinkedData(childData);
|
||||
} else {
|
||||
// tell unknown assignment-client child to exit.
|
||||
qDebug() << "asking unknown child to exit.";
|
||||
QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
nodeList->writeUnverifiedDatagram(diePacket, senderSockAddr);
|
||||
}
|
||||
}
|
||||
|
@ -182,13 +239,9 @@ void AssignmentClientMonitor::readPendingDatagrams() {
|
|||
// update our records about how to reach this child
|
||||
matchingNode->setLocalSocket(senderSockAddr);
|
||||
|
||||
// push past the packet header
|
||||
QDataStream packetStream(receivedPacket);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
|
||||
// decode json
|
||||
QVariantMap unpackedVariantMap;
|
||||
packetStream >> unpackedVariantMap;
|
||||
QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(unpackedVariantMap);
|
||||
QVariantMap packetVariantMap =
|
||||
JSONBreakableMarshal::fromStringBuffer(receivedPacket.mid(numBytesForPacketHeader(receivedPacket)));
|
||||
QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(packetVariantMap);
|
||||
|
||||
// get child's assignment type out of the decoded json
|
||||
QString childType = unpackedStatsJSON["assignment_type"].toString();
|
||||
|
|
|
@ -28,27 +28,37 @@ class AssignmentClientMonitor : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks,
|
||||
const unsigned int maxAssignmentClientForks, QString assignmentPool, QUuid walletUUID,
|
||||
QString assignmentServerHostname, quint16 assignmentServerPort);
|
||||
const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType,
|
||||
QString assignmentPool, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort);
|
||||
~AssignmentClientMonitor();
|
||||
|
||||
|
||||
void stopChildProcesses();
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
void checkSpares();
|
||||
void childProcessFinished();
|
||||
|
||||
public slots:
|
||||
void aboutToQuit();
|
||||
|
||||
private:
|
||||
void spawnChildClient();
|
||||
void simultaneousWaitOnChildren(int waitMsecs);
|
||||
|
||||
QTimer _checkSparesTimer; // every few seconds see if it need fewer or more spare children
|
||||
|
||||
const unsigned int _numAssignmentClientForks;
|
||||
const unsigned int _minAssignmentClientForks;
|
||||
const unsigned int _maxAssignmentClientForks;
|
||||
|
||||
Assignment::Type _requestAssignmentType;
|
||||
QString _assignmentPool;
|
||||
QUuid _walletUUID;
|
||||
QString _assignmentServerHostname;
|
||||
quint16 _assignmentServerPort;
|
||||
|
||||
QMap<qint64, QProcess*> _childProcesses;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentClientMonitor_h
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// AssignmentThread.cpp
|
||||
// assignment-client/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AssignmentThread.h"
|
||||
|
||||
AssignmentThread::AssignmentThread(const SharedAssignmentPointer& assignment, QObject* parent) :
|
||||
QThread(parent),
|
||||
_assignment(assignment)
|
||||
{
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// AssignmentThread.h
|
||||
// assignment-client/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AssignmentThread_h
|
||||
#define hifi_AssignmentThread_h
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
class AssignmentThread : public QThread {
|
||||
public:
|
||||
AssignmentThread(const SharedAssignmentPointer& assignment, QObject* parent);
|
||||
private:
|
||||
SharedAssignmentPointer _assignment;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentThread_h
|
File diff suppressed because it is too large
Load diff
|
@ -29,49 +29,46 @@ class AudioMixer : public ThreadedAssignment {
|
|||
Q_OBJECT
|
||||
public:
|
||||
AudioMixer(const QByteArray& packet);
|
||||
|
||||
void deleteLater() { qDebug() << "DELETE LATER CALLED?"; QObject::deleteLater(); }
|
||||
public slots:
|
||||
/// threaded run of assignment
|
||||
void run();
|
||||
|
||||
|
||||
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
|
||||
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; }
|
||||
|
||||
|
||||
private:
|
||||
/// adds one stream to the mix for a listening node
|
||||
int addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData,
|
||||
const QUuid& streamUUID,
|
||||
PositionalAudioStream* streamToAdd,
|
||||
AvatarAudioStream* listeningNodeStream);
|
||||
|
||||
|
||||
/// prepares and sends a mix to one Node
|
||||
int prepareMixForListeningNode(Node* node);
|
||||
|
||||
|
||||
/// Send Audio Environment packet for a single node
|
||||
void sendAudioEnvironmentPacket(SharedNodePointer node);
|
||||
|
||||
// used on a per stream basis to run the filter on before mixing, large enough to handle the historical
|
||||
// data from a phase delay as well as an entire network buffer
|
||||
int16_t _preMixSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||
|
||||
|
||||
// client samples capacity is larger than what will be sent to optimize mixing
|
||||
// we are MMX adding 4 samples at a time so we need client samples to have an extra 4
|
||||
int16_t _mixSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||
|
||||
void perSecondActions();
|
||||
|
||||
|
||||
bool shouldMute(float quietestFrame);
|
||||
|
||||
QString getReadPendingDatagramsCallsPerSecondsStatsString() const;
|
||||
QString getReadPendingDatagramsPacketsPerCallStatsString() const;
|
||||
QString getReadPendingDatagramsTimeStatsString() const;
|
||||
QString getReadPendingDatagramsHashMatchTimeStatsString() const;
|
||||
|
||||
void parseSettingsObject(const QJsonObject& settingsObject);
|
||||
|
||||
|
||||
float _trailingSleepRatio;
|
||||
float _minAudibilityThreshold;
|
||||
float _performanceThrottlingRatio;
|
||||
|
@ -80,7 +77,7 @@ private:
|
|||
int _numStatFrames;
|
||||
int _sumListeners;
|
||||
int _sumMixes;
|
||||
|
||||
|
||||
QHash<QString, AABox> _audioZones;
|
||||
struct ZonesSettings {
|
||||
QString source;
|
||||
|
@ -94,12 +91,12 @@ private:
|
|||
float wetLevel;
|
||||
};
|
||||
QVector<ReverbSettings> _zoneReverbSettings;
|
||||
|
||||
|
||||
static InboundAudioStream::Settings _streamSettings;
|
||||
|
||||
static bool _printStreamStats;
|
||||
static bool _enableFilter;
|
||||
|
||||
|
||||
quint64 _lastPerSecondCallbackTime;
|
||||
|
||||
bool _sendAudioStreamStats;
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <UUID.h>
|
||||
|
@ -156,7 +157,7 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
quint8 appendFlag = 0;
|
||||
|
||||
// pack header
|
||||
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
||||
int numBytesPacketHeader = nodeList->populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
||||
char* headerEndAt = packet + numBytesPacketHeader;
|
||||
|
||||
// calculate how many stream stat structs we can fit in each packet
|
||||
|
@ -196,70 +197,86 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
}
|
||||
}
|
||||
|
||||
QString AudioMixerClientData::getAudioStreamStatsString() const {
|
||||
QString result;
|
||||
QJsonObject AudioMixerClientData::getAudioStreamStats() const {
|
||||
QJsonObject result;
|
||||
|
||||
QJsonObject downstreamStats;
|
||||
AudioStreamStats streamStats = _downstreamAudioStreamStats;
|
||||
result += "DOWNSTREAM.desired:" + QString::number(streamStats._desiredJitterBufferFrames)
|
||||
+ " available_avg_10s:" + QString::number(streamStats._framesAvailableAverage)
|
||||
+ " available:" + QString::number(streamStats._framesAvailable)
|
||||
+ " starves:" + QString::number(streamStats._starveCount)
|
||||
+ " not_mixed:" + QString::number(streamStats._consecutiveNotMixedCount)
|
||||
+ " overflows:" + QString::number(streamStats._overflowCount)
|
||||
+ " silents_dropped: ?"
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
downstreamStats["desired"] = streamStats._desiredJitterBufferFrames;
|
||||
downstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
downstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
downstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
downstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
downstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
downstreamStats["lost%"] = streamStats._packetStreamStats.getLostRate() * 100.0f;
|
||||
downstreamStats["lost%_30s"] = streamStats._packetStreamWindowStats.getLostRate() * 100.0f;
|
||||
downstreamStats["min_gap"] = formatUsecTime(streamStats._timeGapMin);
|
||||
downstreamStats["max_gap"] = formatUsecTime(streamStats._timeGapMax);
|
||||
downstreamStats["avg_gap"] = formatUsecTime(streamStats._timeGapAverage);
|
||||
downstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
downstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
downstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
result["downstream"] = downstreamStats;
|
||||
|
||||
AvatarAudioStream* avatarAudioStream = getAvatarAudioStream();
|
||||
|
||||
if (avatarAudioStream) {
|
||||
QJsonObject upstreamStats;
|
||||
|
||||
AudioStreamStats streamStats = avatarAudioStream->getAudioStreamStats();
|
||||
result += " UPSTREAM.mic.desired:" + QString::number(streamStats._desiredJitterBufferFrames)
|
||||
+ " desired_calc:" + QString::number(avatarAudioStream->getCalculatedJitterBufferFrames())
|
||||
+ " available_avg_10s:" + QString::number(streamStats._framesAvailableAverage)
|
||||
+ " available:" + QString::number(streamStats._framesAvailable)
|
||||
+ " starves:" + QString::number(streamStats._starveCount)
|
||||
+ " not_mixed:" + QString::number(streamStats._consecutiveNotMixedCount)
|
||||
+ " overflows:" + QString::number(streamStats._overflowCount)
|
||||
+ " silents_dropped:" + QString::number(streamStats._framesDropped)
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
upstreamStats["mic.desired"] = streamStats._desiredJitterBufferFrames;
|
||||
upstreamStats["desired_calc"] = avatarAudioStream->getCalculatedJitterBufferFrames();
|
||||
upstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
upstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
upstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
upstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
upstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
upstreamStats["silents_dropped"] = (double) streamStats._framesDropped;
|
||||
upstreamStats["lost%"] = streamStats._packetStreamStats.getLostRate() * 100.0f;
|
||||
upstreamStats["lost%_30s"] = streamStats._packetStreamWindowStats.getLostRate() * 100.0f;
|
||||
upstreamStats["min_gap"] = formatUsecTime(streamStats._timeGapMin);
|
||||
upstreamStats["max_gap"] = formatUsecTime(streamStats._timeGapMax);
|
||||
upstreamStats["avg_gap"] = formatUsecTime(streamStats._timeGapAverage);
|
||||
upstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
result["upstream"] = upstreamStats;
|
||||
} else {
|
||||
result = "mic unknown";
|
||||
result["upstream"] = "mic unknown";
|
||||
}
|
||||
|
||||
QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
|
||||
QJsonArray injectorArray;
|
||||
for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) {
|
||||
if (i.value()->getType() == PositionalAudioStream::Injector) {
|
||||
QJsonObject upstreamStats;
|
||||
|
||||
AudioStreamStats streamStats = i.value()->getAudioStreamStats();
|
||||
result += " UPSTREAM.inj.desired:" + QString::number(streamStats._desiredJitterBufferFrames)
|
||||
+ " desired_calc:" + QString::number(i.value()->getCalculatedJitterBufferFrames())
|
||||
+ " available_avg_10s:" + QString::number(streamStats._framesAvailableAverage)
|
||||
+ " available:" + QString::number(streamStats._framesAvailable)
|
||||
+ " starves:" + QString::number(streamStats._starveCount)
|
||||
+ " not_mixed:" + QString::number(streamStats._consecutiveNotMixedCount)
|
||||
+ " overflows:" + QString::number(streamStats._overflowCount)
|
||||
+ " silents_dropped:" + QString::number(streamStats._framesDropped)
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
upstreamStats["inj.desired"] = streamStats._desiredJitterBufferFrames;
|
||||
upstreamStats["desired_calc"] = i.value()->getCalculatedJitterBufferFrames();
|
||||
upstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
upstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
upstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
upstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
upstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
upstreamStats["silents_dropped"] = (double) streamStats._framesDropped;
|
||||
upstreamStats["lost%"] = streamStats._packetStreamStats.getLostRate() * 100.0f;
|
||||
upstreamStats["lost%_30s"] = streamStats._packetStreamWindowStats.getLostRate() * 100.0f;
|
||||
upstreamStats["min_gap"] = formatUsecTime(streamStats._timeGapMin);
|
||||
upstreamStats["max_gap"] = formatUsecTime(streamStats._timeGapMax);
|
||||
upstreamStats["avg_gap"] = formatUsecTime(streamStats._timeGapAverage);
|
||||
upstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
injectorArray.push_back(upstreamStats);
|
||||
}
|
||||
}
|
||||
|
||||
result["injectors"] = injectorArray;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_AudioMixerClientData_h
|
||||
#define hifi_AudioMixerClientData_h
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AudioFormat.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||
#include <AudioBuffer.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||
|
@ -46,7 +48,7 @@ public:
|
|||
|
||||
void removeDeadInjectedStreams();
|
||||
|
||||
QString getAudioStreamStatsString() const;
|
||||
QJsonObject getAudioStreamStats() const;
|
||||
|
||||
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode);
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <cfloat>
|
||||
#include <random>
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
@ -20,14 +23,15 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
#include <TryLocker.h>
|
||||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
#include "AvatarMixer.h"
|
||||
|
||||
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||
|
||||
const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / 60.0f) * 1000;
|
||||
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 60;
|
||||
const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000;
|
||||
|
||||
AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
|
@ -45,16 +49,14 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
|||
}
|
||||
|
||||
AvatarMixer::~AvatarMixer() {
|
||||
if (_broadcastTimer) {
|
||||
_broadcastTimer->deleteLater();
|
||||
}
|
||||
|
||||
_broadcastThread.quit();
|
||||
_broadcastThread.wait();
|
||||
}
|
||||
|
||||
void attachAvatarDataToNode(Node* newNode) {
|
||||
if (!newNode->getLinkedData()) {
|
||||
newNode->setLinkedData(new AvatarMixerClientData());
|
||||
}
|
||||
}
|
||||
|
||||
const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f;
|
||||
|
||||
// NOTE: some additional optimizations to consider.
|
||||
|
@ -76,7 +78,12 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
|
||||
// NOTE: The following code calculates the _performanceThrottlingRatio based on how much the avatar-mixer was
|
||||
// able to sleep. This will eventually be used to ask for an additional avatar-mixer to help out. Currently the value
|
||||
// is unused as it is assumed this should not be hit before the avatar-mixer hits the desired bandwidth limit per client.
|
||||
// It is reported in the domain-server stats for the avatar-mixer.
|
||||
|
||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
|
||||
+ (idleTime * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_MSECS);
|
||||
|
||||
|
@ -85,14 +92,14 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
|
||||
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
|
||||
// we're struggling - change our min required loudness to reduce some load
|
||||
// we're struggling - change our performance throttling ratio
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
|
||||
|
||||
qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
|
||||
// we've recovered and can back off the required loudness
|
||||
// we've recovered and can back off the performance throttling
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF;
|
||||
|
||||
if (_performanceThrottlingRatio < 0) {
|
||||
|
@ -100,7 +107,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
}
|
||||
|
||||
qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
}
|
||||
|
||||
|
@ -115,101 +122,243 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
static QByteArray mixedAvatarByteArray;
|
||||
|
||||
int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
int numPacketHeaderBytes = nodeList->populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
|
||||
|
||||
// setup for distributed random floating point values
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::uniform_real_distribution<float> distribution;
|
||||
|
||||
AvatarMixerClientData* nodeData = NULL;
|
||||
AvatarMixerClientData* otherNodeData = NULL;
|
||||
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()
|
||||
&& (nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData()))->getMutex().tryLock()) {
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
if (!node->getLinkedData()) {
|
||||
return false;
|
||||
}
|
||||
if (node->getType() != NodeType::Agent) {
|
||||
return false;
|
||||
}
|
||||
if (!node->getActiveSocket()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
MutexTryLocker lock(nodeData->getMutex());
|
||||
if (!lock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
++_sumListeners;
|
||||
|
||||
// reset packet pointers for this node
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
|
||||
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
glm::vec3 myPosition = avatar.getPosition();
|
||||
|
||||
// reset the internal state for correct random number distribution
|
||||
distribution.reset();
|
||||
|
||||
// reset the max distance for this frame
|
||||
float maxAvatarDistanceThisFrame = 0.0f;
|
||||
|
||||
// reset the number of sent avatars
|
||||
nodeData->resetNumAvatarsSentLastFrame();
|
||||
|
||||
// keep a counter of the number of considered avatars
|
||||
int numOtherAvatars = 0;
|
||||
|
||||
// keep track of outbound data rate specifically for avatar data
|
||||
int numAvatarDataBytes = 0;
|
||||
|
||||
// keep track of the number of other avatars held back in this frame
|
||||
int numAvatarsHeldBack = 0;
|
||||
|
||||
// keep track of the number of other avatar frames skipped
|
||||
int numAvatarsWithSkippedFrames = 0;
|
||||
|
||||
// use the data rate specifically for avatar data for FRD adjustment checks
|
||||
float avatarDataRateLastSecond = nodeData->getOutboundAvatarDataKbps();
|
||||
|
||||
// Check if it is time to adjust what we send this client based on the observed
|
||||
// bandwidth to this node. We do this once a second, which is also the window for
|
||||
// the bandwidth reported by node->getOutboundBandwidth();
|
||||
if (nodeData->getNumFramesSinceFRDAdjustment() > AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) {
|
||||
|
||||
const float FRD_ADJUSTMENT_ACCEPTABLE_RATIO = 0.8f;
|
||||
const float HYSTERISIS_GAP = (1 - FRD_ADJUSTMENT_ACCEPTABLE_RATIO);
|
||||
const float HYSTERISIS_MIDDLE_PERCENTAGE = (1 - (HYSTERISIS_GAP * 0.5f));
|
||||
|
||||
// get the current full rate distance so we can work with it
|
||||
float currentFullRateDistance = nodeData->getFullRateDistance();
|
||||
|
||||
if (avatarDataRateLastSecond > _maxKbpsPerNode) {
|
||||
|
||||
// is the FRD greater than the farthest avatar?
|
||||
// if so, before we calculate anything, set it to that distance
|
||||
currentFullRateDistance = std::min(currentFullRateDistance, nodeData->getMaxAvatarDistance());
|
||||
|
||||
// we're adjusting the full rate distance to target a bandwidth in the middle
|
||||
// of the hysterisis gap
|
||||
currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
|
||||
|
||||
nodeData->setFullRateDistance(currentFullRateDistance);
|
||||
nodeData->resetNumFramesSinceFRDAdjustment();
|
||||
} else if (currentFullRateDistance < nodeData->getMaxAvatarDistance()
|
||||
&& avatarDataRateLastSecond < _maxKbpsPerNode * FRD_ADJUSTMENT_ACCEPTABLE_RATIO) {
|
||||
// we are constrained AND we've recovered to below the acceptable ratio
|
||||
// lets adjust the full rate distance to target a bandwidth in the middle of the hyterisis gap
|
||||
currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
|
||||
|
||||
nodeData->setFullRateDistance(currentFullRateDistance);
|
||||
nodeData->resetNumFramesSinceFRDAdjustment();
|
||||
}
|
||||
} else {
|
||||
nodeData->incrementNumFramesSinceFRDAdjustment();
|
||||
}
|
||||
|
||||
// this is an AGENT we have received head data from
|
||||
// send back a packet with other active node data to this node
|
||||
nodeList->eachNode([&](const SharedNodePointer& otherNode) {
|
||||
if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()
|
||||
&& (otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData()))->getMutex().tryLock()) {
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& otherNode)->bool {
|
||||
if (!otherNode->getLinkedData()) {
|
||||
return false;
|
||||
}
|
||||
if (otherNode->getUUID() == node->getUUID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& otherNode) {
|
||||
++numOtherAvatars;
|
||||
|
||||
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
AvatarData& otherAvatar = otherNodeData->getAvatar();
|
||||
glm::vec3 otherPosition = otherAvatar.getPosition();
|
||||
|
||||
float distanceToAvatar = glm::length(myPosition - otherPosition);
|
||||
// The full rate distance is the distance at which EVERY update will be sent for this avatar
|
||||
// at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
||||
const float FULL_RATE_DISTANCE = 2.0f;
|
||||
|
||||
// Decide whether to send this avatar's data based on it's distance from us
|
||||
if ((_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio))
|
||||
&& (distanceToAvatar == 0.0f || randFloat() < FULL_RATE_DISTANCE / distanceToAvatar)) {
|
||||
QByteArray avatarByteArray;
|
||||
avatarByteArray.append(otherNode->getUUID().toRfc4122());
|
||||
avatarByteArray.append(otherAvatar.toByteArray());
|
||||
|
||||
if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) {
|
||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||
|
||||
// reset the packet
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
}
|
||||
|
||||
// copy the avatar into the mixedAvatarByteArray packet
|
||||
mixedAvatarByteArray.append(avatarByteArray);
|
||||
|
||||
// if the receiving avatar has just connected make sure we send out the mesh and billboard
|
||||
// for this avatar (assuming they exist)
|
||||
bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets();
|
||||
|
||||
// we will also force a send of billboard or identity packet
|
||||
// if either has changed in the last frame
|
||||
|
||||
if (otherNodeData->getBillboardChangeTimestamp() > 0
|
||||
&& (forceSend
|
||||
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|
||||
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||
QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||
billboardPacket.append(otherNode->getUUID().toRfc4122());
|
||||
billboardPacket.append(otherNodeData->getAvatar().getBillboard());
|
||||
nodeList->writeDatagram(billboardPacket, node);
|
||||
|
||||
++_sumBillboardPackets;
|
||||
}
|
||||
|
||||
if (otherNodeData->getIdentityChangeTimestamp() > 0
|
||||
&& (forceSend
|
||||
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|
||||
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||
|
||||
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
||||
|
||||
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
|
||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
|
||||
identityPacket.append(individualData);
|
||||
|
||||
nodeList->writeDatagram(identityPacket, node);
|
||||
|
||||
++_sumIdentityPackets;
|
||||
}
|
||||
MutexTryLocker lock(otherNodeData->getMutex());
|
||||
if (!lock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarData& otherAvatar = otherNodeData->getAvatar();
|
||||
// Decide whether to send this avatar's data based on it's distance from us
|
||||
|
||||
// The full rate distance is the distance at which EVERY update will be sent for this avatar
|
||||
// at twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
||||
glm::vec3 otherPosition = otherAvatar.getPosition();
|
||||
float distanceToAvatar = glm::length(myPosition - otherPosition);
|
||||
|
||||
// potentially update the max full rate distance for this frame
|
||||
maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar);
|
||||
|
||||
if (distanceToAvatar != 0.0f
|
||||
&& distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PacketSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID());
|
||||
PacketSequenceNumber lastSeqFromSender = otherNode->getLastSequenceNumberForPacketType(PacketTypeAvatarData);
|
||||
|
||||
if (lastSeqToReceiver > lastSeqFromSender) {
|
||||
// Did we somehow get out of order packets from the sender?
|
||||
// We don't expect this to happen - in RELEASE we add this to a trackable stat
|
||||
// and in DEBUG we crash on the assert
|
||||
|
||||
otherNodeData->incrementNumOutOfOrderSends();
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// make sure we haven't already sent this data from this sender to this receiver
|
||||
// or that somehow we haven't sent
|
||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||
++numAvatarsHeldBack;
|
||||
return;
|
||||
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
||||
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
||||
++numAvatarsWithSkippedFrames;
|
||||
}
|
||||
|
||||
// we're going to send this avatar
|
||||
|
||||
// increment the number of avatars sent to this reciever
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNode->getLastSequenceNumberForPacketType(PacketTypeAvatarData));
|
||||
|
||||
QByteArray avatarByteArray;
|
||||
avatarByteArray.append(otherNode->getUUID().toRfc4122());
|
||||
avatarByteArray.append(otherAvatar.toByteArray());
|
||||
|
||||
if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) {
|
||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||
|
||||
numAvatarDataBytes += mixedAvatarByteArray.size();
|
||||
|
||||
// reset the packet
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
}
|
||||
|
||||
// copy the avatar into the mixedAvatarByteArray packet
|
||||
mixedAvatarByteArray.append(avatarByteArray);
|
||||
|
||||
// if the receiving avatar has just connected make sure we send out the mesh and billboard
|
||||
// for this avatar (assuming they exist)
|
||||
bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets();
|
||||
|
||||
// we will also force a send of billboard or identity packet
|
||||
// if either has changed in the last frame
|
||||
|
||||
if (otherNodeData->getBillboardChangeTimestamp() > 0
|
||||
&& (forceSend
|
||||
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|
||||
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||
QByteArray billboardPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||
billboardPacket.append(otherNode->getUUID().toRfc4122());
|
||||
billboardPacket.append(otherNodeData->getAvatar().getBillboard());
|
||||
|
||||
nodeList->writeDatagram(billboardPacket, node);
|
||||
|
||||
++_sumBillboardPackets;
|
||||
}
|
||||
|
||||
if (otherNodeData->getIdentityChangeTimestamp() > 0
|
||||
&& (forceSend
|
||||
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|
||||
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||
|
||||
QByteArray identityPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
||||
|
||||
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
|
||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
|
||||
identityPacket.append(individualData);
|
||||
|
||||
nodeList->writeDatagram(identityPacket, node);
|
||||
|
||||
++_sumIdentityPackets;
|
||||
}
|
||||
|
||||
otherNodeData->getMutex().unlock();
|
||||
}
|
||||
});
|
||||
|
||||
// send the last packet
|
||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||
|
||||
nodeData->getMutex().unlock();
|
||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||
nodeData->recordSentAvatarData(numAvatarDataBytes + mixedAvatarByteArray.size());
|
||||
|
||||
// record the number of avatars held back this frame
|
||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||
|
||||
if (numOtherAvatars == 0) {
|
||||
// update the full rate distance to FLOAT_MAX since we didn't have any other avatars to send
|
||||
nodeData->setMaxAvatarDistance(FLT_MAX);
|
||||
} else {
|
||||
nodeData->setMaxAvatarDistance(maxAvatarDistanceThisFrame);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
@ -217,13 +366,36 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||
if (killedNode->getType() == NodeType::Agent
|
||||
&& killedNode->getLinkedData()) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// this was an avatar we were sending to other people
|
||||
// send a kill packet for it to our other nodes
|
||||
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
|
||||
QByteArray killPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
|
||||
killPacket += killedNode->getUUID().toRfc4122();
|
||||
|
||||
DependencyManager::get<NodeList>()->broadcastToNodes(killPacket,
|
||||
NodeSet() << NodeType::Agent);
|
||||
nodeList->broadcastToNodes(killPacket, NodeSet() << NodeType::Agent);
|
||||
|
||||
// we also want to remove sequence number data for this avatar on our other avatars
|
||||
// so invoke the appropriate method on the AvatarMixerClientData for other avatars
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
if (!node->getLinkedData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node->getUUID() == killedNode->getUUID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
QMetaObject::invokeMethod(node->getLinkedData(),
|
||||
"removeLastBroadcastSequenceNumber",
|
||||
Qt::AutoConnection,
|
||||
Q_ARG(const QUuid&, QUuid(killedNode->getUUID())));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,9 +469,38 @@ void AvatarMixer::sendStatsPacket() {
|
|||
|
||||
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100;
|
||||
statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
|
||||
|
||||
QJsonObject avatarsObject;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
// add stats for each listerner
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
QJsonObject avatarStats;
|
||||
|
||||
const QString NODE_OUTBOUND_KBPS_STAT_KEY = "outbound_kbps";
|
||||
|
||||
// add the key to ask the domain-server for a username replacement, if it has it
|
||||
avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth();
|
||||
|
||||
AvatarMixerClientData* clientData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
MutexTryLocker lock(clientData->getMutex());
|
||||
if (lock.isLocked()) {
|
||||
clientData->loadJSONStats(avatarStats);
|
||||
|
||||
// add the diff between the full outbound bandwidth and the measured bandwidth for AvatarData send only
|
||||
avatarStats["delta_full_vs_avatar_data_kbps"] =
|
||||
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY].toDouble() - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats;
|
||||
});
|
||||
|
||||
statsObject["avatars"] = avatarsObject;
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
|
||||
|
||||
_sumListeners = 0;
|
||||
_sumBillboardPackets = 0;
|
||||
_sumIdentityPackets = 0;
|
||||
|
@ -312,17 +513,54 @@ void AvatarMixer::run() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
|
||||
nodeList->linkedDataCreateCallback = [] (Node* node) {
|
||||
node->setLinkedData(new AvatarMixerClientData());
|
||||
};
|
||||
|
||||
// setup the timer that will be fired on the broadcast thread
|
||||
QTimer* broadcastTimer = new QTimer();
|
||||
broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS);
|
||||
broadcastTimer->moveToThread(&_broadcastThread);
|
||||
_broadcastTimer = new QTimer;
|
||||
_broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS);
|
||||
_broadcastTimer->moveToThread(&_broadcastThread);
|
||||
|
||||
// connect appropriate signals and slots
|
||||
connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
|
||||
connect(&_broadcastThread, SIGNAL(started()), broadcastTimer, SLOT(start()));
|
||||
connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
|
||||
connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start()));
|
||||
|
||||
// wait until we have the domain-server settings, otherwise we bail
|
||||
DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
qDebug() << "Waiting for domain settings from domain-server.";
|
||||
|
||||
// block until we get the settingsRequestComplete signal
|
||||
QEventLoop loop;
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
|
||||
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
|
||||
domainHandler.requestDomainSettings();
|
||||
loop.exec();
|
||||
|
||||
if (domainHandler.getSettingsObject().isEmpty()) {
|
||||
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
|
||||
setFinished(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the settings to pull out the values we need
|
||||
parseDomainServerSettings(domainHandler.getSettingsObject());
|
||||
|
||||
// start the broadcastThread
|
||||
_broadcastThread.start();
|
||||
}
|
||||
|
||||
void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||
const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer";
|
||||
const QString NODE_SEND_BANDWIDTH_KEY = "max_node_send_bandwidth";
|
||||
|
||||
const float DEFAULT_NODE_SEND_BANDWIDTH = 1.0f;
|
||||
QJsonValue nodeBandwidthValue = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject()[NODE_SEND_BANDWIDTH_KEY];
|
||||
if (!nodeBandwidthValue.isDouble()) {
|
||||
qDebug() << NODE_SEND_BANDWIDTH_KEY << "is not a double - will continue with default value";
|
||||
}
|
||||
|
||||
_maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA;
|
||||
qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps.";
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ public slots:
|
|||
|
||||
private:
|
||||
void broadcastAvatarData();
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
||||
QThread _broadcastThread;
|
||||
|
||||
|
@ -47,6 +48,10 @@ private:
|
|||
int _numStatFrames;
|
||||
int _sumBillboardPackets;
|
||||
int _sumIdentityPackets;
|
||||
|
||||
float _maxKbpsPerNode = 0.0f;
|
||||
|
||||
QTimer* _broadcastTimer = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixer_h
|
||||
|
|
|
@ -13,15 +13,6 @@
|
|||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
AvatarMixerClientData::AvatarMixerClientData() :
|
||||
NodeData(),
|
||||
_hasReceivedFirstPackets(false),
|
||||
_billboardChangeTimestamp(0),
|
||||
_identityChangeTimestamp(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int AvatarMixerClientData::parseData(const QByteArray& packet) {
|
||||
// compute the offset to the data payload
|
||||
int offset = numBytesForPacketHeader(packet);
|
||||
|
@ -33,3 +24,25 @@ bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() {
|
|||
_hasReceivedFirstPackets = true;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
PacketSequenceNumber AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const {
|
||||
// return the matching PacketSequenceNumber, or the default if we don't have it
|
||||
auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID);
|
||||
if (nodeMatch != _lastBroadcastSequenceNumbers.end()) {
|
||||
return nodeMatch->second;
|
||||
} else {
|
||||
return DEFAULT_SEQUENCE_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
||||
jsonObject["display_name"] = _avatar.getDisplayName();
|
||||
jsonObject["full_rate_distance"] = _fullRateDistance;
|
||||
jsonObject["max_avatar_distance"] = _maxAvatarDistance;
|
||||
jsonObject["num_avatars_sent_last_frame"] = _numAvatarsSentLastFrame;
|
||||
jsonObject["avg_other_avatar_starves_per_second"] = getAvgNumOtherAvatarStarvesPerSecond();
|
||||
jsonObject["avg_other_avatar_skips_per_second"] = getAvgNumOtherAvatarSkipsPerSecond();
|
||||
jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends;
|
||||
|
||||
jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps();
|
||||
}
|
||||
|
|
|
@ -12,32 +12,89 @@
|
|||
#ifndef hifi_AvatarMixerClientData_h
|
||||
#define hifi_AvatarMixerClientData_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <AvatarData.h>
|
||||
#include <NodeData.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
|
||||
|
||||
class AvatarMixerClientData : public NodeData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AvatarMixerClientData();
|
||||
|
||||
int parseData(const QByteArray& packet);
|
||||
AvatarData& getAvatar() { return _avatar; }
|
||||
|
||||
bool checkAndSetHasReceivedFirstPackets();
|
||||
|
||||
|
||||
PacketSequenceNumber getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const;
|
||||
void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, PacketSequenceNumber sequenceNumber)
|
||||
{ _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; }
|
||||
Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); }
|
||||
|
||||
quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; }
|
||||
void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; }
|
||||
|
||||
quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
|
||||
void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; }
|
||||
|
||||
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
|
||||
float getFullRateDistance() const { return _fullRateDistance; }
|
||||
|
||||
void setMaxAvatarDistance(float maxAvatarDistance) { _maxAvatarDistance = maxAvatarDistance; }
|
||||
float getMaxAvatarDistance() const { return _maxAvatarDistance; }
|
||||
|
||||
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
||||
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
||||
int getNumAvatarsSentLastFrame() const { return _numAvatarsSentLastFrame; }
|
||||
|
||||
void recordNumOtherAvatarStarves(int numAvatarsHeldBack) { _otherAvatarStarves.updateAverage((float) numAvatarsHeldBack); }
|
||||
float getAvgNumOtherAvatarStarvesPerSecond() const { return _otherAvatarStarves.getAverageSampleValuePerSecond(); }
|
||||
|
||||
void recordNumOtherAvatarSkips(int numOtherAvatarSkips) { _otherAvatarSkips.updateAverage((float) numOtherAvatarSkips); }
|
||||
float getAvgNumOtherAvatarSkipsPerSecond() const { return _otherAvatarSkips.getAverageSampleValuePerSecond(); }
|
||||
|
||||
void incrementNumOutOfOrderSends() { ++_numOutOfOrderSends; }
|
||||
|
||||
int getNumFramesSinceFRDAdjustment() const { return _numFramesSinceAdjustment; }
|
||||
void incrementNumFramesSinceFRDAdjustment() { ++_numFramesSinceAdjustment; }
|
||||
void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; }
|
||||
|
||||
void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); }
|
||||
|
||||
float getOutboundAvatarDataKbps() const
|
||||
{ return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; }
|
||||
|
||||
void loadJSONStats(QJsonObject& jsonObject) const;
|
||||
private:
|
||||
AvatarData _avatar;
|
||||
bool _hasReceivedFirstPackets;
|
||||
quint64 _billboardChangeTimestamp;
|
||||
quint64 _identityChangeTimestamp;
|
||||
|
||||
std::unordered_map<QUuid, PacketSequenceNumber, UUIDHasher> _lastBroadcastSequenceNumbers;
|
||||
|
||||
bool _hasReceivedFirstPackets = false;
|
||||
quint64 _billboardChangeTimestamp = 0;
|
||||
quint64 _identityChangeTimestamp = 0;
|
||||
|
||||
float _fullRateDistance = FLT_MAX;
|
||||
float _maxAvatarDistance = FLT_MAX;
|
||||
|
||||
int _numAvatarsSentLastFrame = 0;
|
||||
int _numFramesSinceAdjustment = 0;
|
||||
|
||||
SimpleMovingAverage _otherAvatarStarves;
|
||||
SimpleMovingAverage _otherAvatarSkips;
|
||||
int _numOutOfOrderSends = 0;
|
||||
|
||||
SimpleMovingAverage _avgOtherAvatarDataRate;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixerClientData_h
|
||||
|
|
|
@ -27,6 +27,11 @@ EntityServer::EntityServer(const QByteArray& packet)
|
|||
}
|
||||
|
||||
EntityServer::~EntityServer() {
|
||||
if (_pruneDeletedEntitiesTimer) {
|
||||
_pruneDeletedEntitiesTimer->stop();
|
||||
_pruneDeletedEntitiesTimer->deleteLater();
|
||||
}
|
||||
|
||||
EntityTree* tree = (EntityTree*)_tree;
|
||||
tree->removeNewlyCreatedHook(this);
|
||||
}
|
||||
|
@ -48,10 +53,10 @@ Octree* EntityServer::createTree() {
|
|||
}
|
||||
|
||||
void EntityServer::beforeRun() {
|
||||
QTimer* pruneDeletedEntitiesTimer = new QTimer(this);
|
||||
connect(pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
|
||||
_pruneDeletedEntitiesTimer = new QTimer();
|
||||
connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
|
||||
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
|
||||
pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
|
||||
_pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
|
||||
}
|
||||
|
||||
void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) {
|
||||
|
@ -59,7 +64,9 @@ void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePo
|
|||
unsigned char outputBuffer[MAX_PACKET_SIZE];
|
||||
unsigned char* copyAt = outputBuffer;
|
||||
|
||||
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeEntityAddResponse);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
int numBytesPacketHeader = nodeList->populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeEntityAddResponse);
|
||||
int packetLength = numBytesPacketHeader;
|
||||
copyAt += numBytesPacketHeader;
|
||||
|
||||
|
@ -76,7 +83,7 @@ void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePo
|
|||
copyAt += sizeof(entityID);
|
||||
packetLength += sizeof(entityID);
|
||||
|
||||
DependencyManager::get<NodeList>()->writeDatagram((char*) outputBuffer, packetLength, senderNode);
|
||||
nodeList->writeDatagram((char*) outputBuffer, packetLength, senderNode);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ protected:
|
|||
|
||||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
QTimer* _pruneDeletedEntitiesTimer = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -9,10 +9,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "AssignmentClientApp.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
AssignmentClientApp app(argc, argv);
|
||||
return 0;
|
||||
|
||||
int acReturn = app.exec();
|
||||
qDebug() << "assignment-client process" << app.applicationPid() << "exiting with status code" << acReturn;
|
||||
|
||||
return acReturn;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
//
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
|
@ -275,9 +277,11 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
|
||||
char* dataAt = packet;
|
||||
int bytesRemaining = MAX_PACKET_SIZE;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// pack header
|
||||
int numBytesPacketHeader = populatePacketHeader(packet, _myServer->getMyEditNackType());
|
||||
int numBytesPacketHeader = nodeList->populatePacketHeader(packet, _myServer->getMyEditNackType());
|
||||
dataAt += numBytesPacketHeader;
|
||||
bytesRemaining -= numBytesPacketHeader;
|
||||
|
||||
|
@ -299,7 +303,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
numSequenceNumbersAvailable -= numSequenceNumbers;
|
||||
|
||||
// send it
|
||||
DependencyManager::get<NodeList>()->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode);
|
||||
nodeList->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode);
|
||||
packetsSent++;
|
||||
|
||||
qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID;
|
||||
|
|
|
@ -74,7 +74,7 @@ public:
|
|||
|
||||
NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; }
|
||||
|
||||
void shuttingDown() { _shuttingDown = true;}
|
||||
virtual void terminating() { _shuttingDown = true; ReceivedPacketProcessor::terminating(); }
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -9,11 +9,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "PacketHeaders.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "OctreeQueryNode.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "OctreeSendThread.h"
|
||||
|
||||
OctreeQueryNode::OctreeQueryNode() :
|
||||
|
@ -91,8 +95,8 @@ void OctreeQueryNode::sendThreadFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeQueryNode::initializeOctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node) {
|
||||
_octreeSendThread = new OctreeSendThread(myAssignment, node);
|
||||
void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) {
|
||||
_octreeSendThread = new OctreeSendThread(myServer, node);
|
||||
|
||||
// we want to be notified when the thread finishes
|
||||
connect(_octreeSendThread, &GenericThread::finished, this, &OctreeQueryNode::sendThreadFinished);
|
||||
|
@ -189,7 +193,9 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
}
|
||||
|
||||
_octreePacketAvailableBytes = MAX_PACKET_SIZE;
|
||||
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(_octreePacket), _myPacketType);
|
||||
int numBytesPacketHeader = DependencyManager::get<NodeList>()->populatePacketHeader(reinterpret_cast<char*>(_octreePacket),
|
||||
_myPacketType);
|
||||
|
||||
_octreePacketAt = _octreePacket + numBytesPacketHeader;
|
||||
_octreePacketAvailableBytes -= numBytesPacketHeader;
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
#include <OctreePacketData.h>
|
||||
#include <OctreeQuery.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <ThreadedAssignment.h> // for SharedAssignmentPointer
|
||||
#include "SentPacketHistory.h"
|
||||
#include <qqueue.h>
|
||||
|
||||
class OctreeSendThread;
|
||||
class OctreeServer;
|
||||
|
||||
class OctreeQueryNode : public OctreeQuery {
|
||||
Q_OBJECT
|
||||
|
@ -89,7 +89,7 @@ public:
|
|||
|
||||
OctreeSceneStats stats;
|
||||
|
||||
void initializeOctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node);
|
||||
void initializeOctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
|
||||
bool isOctreeSendThreadInitalized() { return _octreeSendThread; }
|
||||
|
||||
void dumpOutOfView();
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
//
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreeSendThread.h"
|
||||
#include "OctreeServer.h"
|
||||
|
@ -21,9 +21,8 @@
|
|||
quint64 startSceneSleepTime = 0;
|
||||
quint64 endSceneSleepTime = 0;
|
||||
|
||||
OctreeSendThread::OctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node) :
|
||||
_myAssignment(myAssignment),
|
||||
_myServer(static_cast<OctreeServer*>(myAssignment.data())),
|
||||
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
|
||||
_myServer(myServer),
|
||||
_node(node),
|
||||
_nodeUUID(node->getUUID()),
|
||||
_packetData(),
|
||||
|
@ -31,9 +30,14 @@ OctreeSendThread::OctreeSendThread(const SharedAssignmentPointer& myAssignment,
|
|||
_isShuttingDown(false)
|
||||
{
|
||||
QString safeServerName("Octree");
|
||||
|
||||
// set our QThread object name so we can identify this thread while debugging
|
||||
setObjectName(QString("Octree Send Thread (%1)").arg(uuidStringWithoutCurlyBraces(node->getUUID())));
|
||||
|
||||
if (_myServer) {
|
||||
safeServerName = _myServer->getMyServerName();
|
||||
}
|
||||
|
||||
qDebug() << qPrintable(safeServerName) << "server [" << _myServer << "]: client connected "
|
||||
"- starting sending thread [" << this << "]";
|
||||
|
||||
|
@ -53,7 +57,6 @@ OctreeSendThread::~OctreeSendThread() {
|
|||
OctreeServer::stopTrackingThread(this);
|
||||
|
||||
_node.clear();
|
||||
_myAssignment.clear();
|
||||
}
|
||||
|
||||
void OctreeSendThread::setIsShuttingDown() {
|
||||
|
@ -66,15 +69,13 @@ bool OctreeSendThread::process() {
|
|||
return false; // exit early if we're shutting down
|
||||
}
|
||||
|
||||
// check that our server and assignment is still valid
|
||||
if (!_myServer || !_myAssignment) {
|
||||
return false; // exit early if it's not, it means the server is shutting down
|
||||
}
|
||||
|
||||
OctreeServer::didProcess(this);
|
||||
|
||||
quint64 start = usecTimestampNow();
|
||||
|
||||
// we'd better have a server at this point, or we're in trouble
|
||||
assert(_myServer);
|
||||
|
||||
// don't do any send processing until the initial load of the octree is complete...
|
||||
if (_myServer->isInitialLoadComplete()) {
|
||||
if (_node) {
|
||||
|
@ -290,7 +291,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
}
|
||||
|
||||
// calculate max number of packets that can be sent during this interval
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||
|
||||
int truePacketsSent = 0;
|
||||
|
|
|
@ -26,7 +26,7 @@ class OctreeServer;
|
|||
class OctreeSendThread : public GenericThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node);
|
||||
OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
|
||||
virtual ~OctreeSendThread();
|
||||
|
||||
void setIsShuttingDown();
|
||||
|
@ -43,7 +43,6 @@ protected:
|
|||
virtual bool process();
|
||||
|
||||
private:
|
||||
SharedAssignmentPointer _myAssignment;
|
||||
OctreeServer* _myServer;
|
||||
SharedNodePointer _node;
|
||||
QUuid _nodeUUID;
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include <AccountManager.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <LogHandler.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "../AssignmentClient.h"
|
||||
|
@ -211,14 +213,6 @@ void OctreeServer::trackProcessWaitTime(float time) {
|
|||
_averageProcessWaitTime.updateAverage(time);
|
||||
}
|
||||
|
||||
void OctreeServer::attachQueryNodeToNode(Node* newNode) {
|
||||
if (!newNode->getLinkedData() && _instance) {
|
||||
OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode();
|
||||
newQueryNodeData->init();
|
||||
newNode->setLinkedData(newQueryNodeData);
|
||||
}
|
||||
}
|
||||
|
||||
OctreeServer::OctreeServer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
_argc(0),
|
||||
|
@ -252,7 +246,7 @@ OctreeServer::OctreeServer(const QByteArray& packet) :
|
|||
|
||||
// make sure the AccountManager has an Auth URL for payment redemptions
|
||||
|
||||
AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
|
||||
AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||
}
|
||||
|
||||
OctreeServer::~OctreeServer() {
|
||||
|
@ -265,16 +259,19 @@ OctreeServer::~OctreeServer() {
|
|||
}
|
||||
|
||||
if (_jurisdictionSender) {
|
||||
_jurisdictionSender->terminating();
|
||||
_jurisdictionSender->terminate();
|
||||
_jurisdictionSender->deleteLater();
|
||||
}
|
||||
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
_octreeInboundPacketProcessor->terminating();
|
||||
_octreeInboundPacketProcessor->terminate();
|
||||
_octreeInboundPacketProcessor->deleteLater();
|
||||
}
|
||||
|
||||
if (_persistThread) {
|
||||
_persistThread->terminating();
|
||||
_persistThread->terminate();
|
||||
_persistThread->deleteLater();
|
||||
}
|
||||
|
@ -831,42 +828,42 @@ void OctreeServer::parsePayload() {
|
|||
|
||||
void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// If we know we're shutting down we just drop these packets on the floor.
|
||||
// This stops us from initializing send threads we just shut down.
|
||||
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
PacketType packetType = packetTypeForPacket(receivedPacket);
|
||||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (packetType == getMyQueryMessageType()) {
|
||||
// If we got a query packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*)matchingNode->getLinkedData();
|
||||
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
|
||||
if (!_isShuttingDown) {
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
PacketType packetType = packetTypeForPacket(receivedPacket);
|
||||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (packetType == getMyQueryMessageType()) {
|
||||
// If we got a query packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
|
||||
|
||||
// NOTE: this is an important aspect of the proper ref counting. The send threads/node data need to
|
||||
// know that the OctreeServer/Assignment will not get deleted on it while it's still active. The
|
||||
// solution is to get the shared pointer for the current assignment. We need to make sure this is the
|
||||
// same SharedAssignmentPointer that was ref counted by the assignment client.
|
||||
SharedAssignmentPointer sharedAssignment = AssignmentClient::getCurrentAssignment();
|
||||
nodeData->initializeOctreeSendThread(sharedAssignment, matchingNode);
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData();
|
||||
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
|
||||
nodeData->initializeOctreeSendThread(this, matchingNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (packetType == PacketTypeOctreeDataNack) {
|
||||
// If we got a nack packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*)matchingNode->getLinkedData();
|
||||
if (nodeData) {
|
||||
nodeData->parseNackPacket(receivedPacket);
|
||||
} else if (packetType == PacketTypeOctreeDataNack) {
|
||||
// If we got a nack packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*)matchingNode->getLinkedData();
|
||||
if (nodeData) {
|
||||
nodeData->parseNackPacket(receivedPacket);
|
||||
}
|
||||
}
|
||||
} else if (packetType == PacketTypeJurisdictionRequest) {
|
||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else {
|
||||
// let processNodeData handle it.
|
||||
DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket);
|
||||
}
|
||||
} else if (packetType == PacketTypeJurisdictionRequest) {
|
||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else {
|
||||
// let processNodeData handle it.
|
||||
DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1036,6 +1033,13 @@ void OctreeServer::readConfiguration() {
|
|||
strcpy(_persistFilename, qPrintable(persistFilename));
|
||||
qDebug("persistFilename=%s", _persistFilename);
|
||||
|
||||
QString persistAsFileType;
|
||||
if (!readOptionString(QString("persistAsFileType"), settingsSectionObject, persistAsFileType)) {
|
||||
persistAsFileType = "svo";
|
||||
}
|
||||
_persistAsFileType = persistAsFileType;
|
||||
qDebug() << "persistAsFileType=" << _persistAsFileType;
|
||||
|
||||
_persistInterval = OctreePersistThread::DEFAULT_PERSIST_INTERVAL;
|
||||
readOptionInt(QString("persistInterval"), settingsSectionObject, _persistInterval);
|
||||
qDebug() << "persistInterval=" << _persistInterval;
|
||||
|
@ -1087,8 +1091,6 @@ void OctreeServer::readConfiguration() {
|
|||
}
|
||||
|
||||
void OctreeServer::run() {
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
|
||||
_safeServerName = getMyServerName();
|
||||
|
||||
// Before we do anything else, create our tree...
|
||||
|
@ -1122,7 +1124,11 @@ void OctreeServer::run() {
|
|||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
||||
nodeList->linkedDataCreateCallback = &OctreeServer::attachQueryNodeToNode;
|
||||
nodeList->linkedDataCreateCallback = [] (Node* node) {
|
||||
OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode();
|
||||
newQueryNodeData->init();
|
||||
node->setLinkedData(newQueryNodeData);
|
||||
};
|
||||
|
||||
srand((unsigned)time(0));
|
||||
|
||||
|
@ -1131,7 +1137,7 @@ void OctreeServer::run() {
|
|||
|
||||
// now set up PersistThread
|
||||
_persistThread = new OctreePersistThread(_tree, _persistFilename, _persistInterval,
|
||||
_wantBackup, _settings, _debugTimestampNow);
|
||||
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
|
||||
if (_persistThread) {
|
||||
_persistThread->initialize(true);
|
||||
}
|
||||
|
@ -1210,16 +1216,34 @@ void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
|
|||
|
||||
void OctreeServer::aboutToFinish() {
|
||||
qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
|
||||
|
||||
_isShuttingDown = true;
|
||||
|
||||
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
|
||||
_octreeInboundPacketProcessor->shuttingDown();
|
||||
|
||||
// we're going down - set the NodeList linkedDataCallback to NULL so we do not create any more OctreeQueryNode objects.
|
||||
// This ensures that when we forceNodeShutdown below for each node we don't get any more newly connecting nodes
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->linkedDataCreateCallback = NULL;
|
||||
|
||||
DependencyManager::get<NodeList>()->eachNode([this](const SharedNodePointer& node) {
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
_octreeInboundPacketProcessor->terminating();
|
||||
}
|
||||
|
||||
if (_jurisdictionSender) {
|
||||
_jurisdictionSender->terminating();
|
||||
}
|
||||
|
||||
// force a shutdown of all of our OctreeSendThreads - at this point it has to be impossible for a
|
||||
// linkedDataCreateCallback to be called for a new node
|
||||
nodeList->eachNode([this](const SharedNodePointer& node) {
|
||||
qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
|
||||
forceNodeShutdown(node);
|
||||
});
|
||||
|
||||
if (_persistThread) {
|
||||
_persistThread->aboutToFinish();
|
||||
_persistThread->terminating();
|
||||
}
|
||||
|
||||
qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish...";
|
||||
|
|
|
@ -75,8 +75,6 @@ public:
|
|||
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; }
|
||||
virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; }
|
||||
|
||||
static void attachQueryNodeToNode(Node* newNode);
|
||||
|
||||
static float SKIP_TIME; // use this for trackXXXTime() calls for non-times
|
||||
|
||||
static void trackLoopTime(float time) { _averageLoopTime.updateAverage(time); }
|
||||
|
@ -146,17 +144,20 @@ protected:
|
|||
QString getStatusLink();
|
||||
|
||||
void setupDatagramProcessingThread();
|
||||
|
||||
|
||||
int _argc;
|
||||
const char** _argv;
|
||||
char** _parsedArgV;
|
||||
QJsonObject _settings;
|
||||
|
||||
bool _isShuttingDown = false;
|
||||
|
||||
HTTPManager* _httpManager;
|
||||
int _statusPort;
|
||||
QString _statusHost;
|
||||
|
||||
char _persistFilename[MAX_FILENAME_LENGTH];
|
||||
QString _persistAsFileType;
|
||||
int _packetsPerClientPerInterval;
|
||||
int _packetsTotalPerInterval;
|
||||
Octree* _tree; // this IS a reaveraging tree
|
||||
|
|
85
cmake/externals/LibOVR/CMakeLists.txt
vendored
Normal file
85
cmake/externals/LibOVR/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
set(EXTERNAL_NAME LibOVR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://static.oculus.com/sdk-downloads/ovr_sdk_win_0.5.0.1.zip
|
||||
URL_MD5 d3fc4c02db9be5ff08af4ef4c97b32f9
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Windows/Win32/Release/VS2013/LibOVR.lib CACHE TYPE INTERNAL)
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://static.oculus.com/sdk-downloads/ovr_sdk_macos_0.5.0.1.tar.gz
|
||||
URL_MD5 0a0785a04fb285f64f62267388344ad6
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
# In theory we should use the Headers path inside the framework, as seen here
|
||||
# but unfortunately Oculus doesn't seem to have figured out automated testing
|
||||
# so they released a framework with missing headers.
|
||||
#set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE TYPE INTERNAL)
|
||||
|
||||
# Work around the broken framework by using a different path for the headers.
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE TYPE INTERNAL)
|
||||
|
||||
|
||||
|
||||
elseif(NOT ANDROID)
|
||||
|
||||
# http://static.oculus.com/sdk-downloads/ovr_sdk_linux_0.4.4.tar.xz
|
||||
# ec3bd8cff4a1461b4e21210e7feb0572
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
PREFIX ${EXTERNAL_NAME}
|
||||
GIT_REPOSITORY https://github.com/jherico/OculusSDK.git
|
||||
GIT_TAG 0d6f0cf110ea7566fc6d64b8d4fe6bb881d9cff5
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
LOG_DOWNLOAD ON
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE TYPE INTERNAL)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
|
||||
# Check for XRandR (modern resolution switching and gamma control)
|
||||
if (NOT X11_Xrandr_FOUND)
|
||||
message(FATAL_ERROR "The RandR library and headers were not found")
|
||||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS rt udev ${CMAKE_THREAD_LIBS_INIT} ${X11_X11_LIB} ${X11_Xrandr_LIB})
|
||||
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE TYPE INTERNAL)
|
||||
endif()
|
||||
|
||||
|
2
cmake/externals/bullet/CMakeLists.txt
vendored
2
cmake/externals/bullet/CMakeLists.txt
vendored
|
@ -19,7 +19,7 @@ if (WIN32)
|
|||
${EXTERNAL_NAME}
|
||||
URL https://bullet.googlecode.com/files/bullet-2.82-r2704.zip
|
||||
URL_MD5 f5e8914fc9064ad32e0d62d19d33d977
|
||||
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_DEMOS=0 -DUSE_GLUT=0
|
||||
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_DEMOS=0 -DUSE_GLUT=0 -DUSE_DX11=0
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
|
|
2
cmake/externals/glm/CMakeLists.txt
vendored
2
cmake/externals/glm/CMakeLists.txt
vendored
|
@ -3,7 +3,7 @@ set(EXTERNAL_NAME glm)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://pkgs.fedoraproject.org/repo/pkgs/glm/glm-0.9.5.4.zip/fab76fc982b256b46208e5c750ed456a/glm-0.9.5.4.zip
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.5.4.zip
|
||||
URL_MD5 fab76fc982b256b46208e5c750ed456a
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
|
|
38
cmake/externals/openvr/CMakeLists.txt
vendored
Normal file
38
cmake/externals/openvr/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
set(EXTERNAL_NAME OpenVR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://github.com/ValveSoftware/openvr/archive/0.9.0.zip
|
||||
URL_MD5 4fbde7759f604aaa68b9c40d628cc34a
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/headers CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win32/openvr_api.lib CACHE TYPE INTERNAL)
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL)
|
||||
|
||||
elseif(NOT ANDROID)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL)
|
||||
|
||||
endif()
|
||||
|
39
cmake/externals/tbb/CMakeLists.txt
vendored
39
cmake/externals/tbb/CMakeLists.txt
vendored
|
@ -8,31 +8,44 @@ if (ANDROID)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150209oss_src.tgz
|
||||
URL_MD5 f09c9abe8ec74e6558c1f89cebbe2893
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz
|
||||
URL_MD5 bf090eaa86cf89ea014b7b462786a440
|
||||
BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm
|
||||
BUILD_IN_SOURCE 1
|
||||
CONFIGURE_COMMAND ""
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/AndroidTBBLibCopy.cmake
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=so -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
elseif (APPLE)
|
||||
find_program(MAKE_COMMAND NAMES make DOC "Path to the make command")
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz
|
||||
URL_MD5 bf090eaa86cf89ea014b7b462786a440
|
||||
BUILD_COMMAND ${MAKE_COMMAND} tbb_os=macos
|
||||
BUILD_IN_SOURCE 1
|
||||
CONFIGURE_COMMAND ""
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=dylib -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
else ()
|
||||
if (APPLE)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150209oss_osx.tgz)
|
||||
set(DOWNLOAD_MD5 3e683c19792582b61382e0d760ea5db2)
|
||||
elseif (WIN32)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150209oss_win.zip)
|
||||
set(DOWNLOAD_MD5 e19c184f2bb0e944fc5f397f1e34ca84)
|
||||
if (WIN32)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip)
|
||||
set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440)
|
||||
else ()
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150209oss_lin.tgz)
|
||||
set(DOWNLOAD_MD5 d9c2a6f7807df364be44a8c3c05e8457)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_lin.tgz)
|
||||
set(DOWNLOAD_MD5 7830ba2bc62438325fba2ec0c95367a5)
|
||||
endif ()
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL ${DOWNLOAD_URL}
|
||||
URL_MD5 ${DOWNLOAD_MD5}
|
||||
BUILD_COMMAND ""
|
||||
CONFIGURE_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
@ -45,7 +58,7 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
|||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (APPLE)
|
||||
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/libc++")
|
||||
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib")
|
||||
set(_LIB_PREFIX "lib")
|
||||
set(_LIB_EXT "dylib")
|
||||
|
||||
|
@ -95,8 +108,8 @@ endif ()
|
|||
|
||||
if (DEFINED _TBB_LIB_DIR)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb.${_LIB_EXT} CACHE FILEPATH "TBB release library location")
|
||||
set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb.${_LIB_EXT} CACHE FILEPATH "TBB release library location")
|
||||
set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc.${_LIB_EXT} CACHE FILEPATH "TBB malloc release library location")
|
||||
endif ()
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
|
||||
# first find the so files in the source dir
|
||||
set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/libc++)
|
||||
set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
|
||||
file(GLOB_RECURSE _TBB_LIBRARIES "${_TBB_LIBRARY_DIR}/*.dylib")
|
||||
|
||||
# raise an error if we found none
|
||||
|
@ -28,20 +28,6 @@ find_program(LIPO_COMMAND NAMES lipo DOC "Path to the lipo command")
|
|||
foreach(_TBB_LIBRARY ${_TBB_LIBRARIES})
|
||||
get_filename_component(_TBB_LIBRARY_FILENAME ${_TBB_LIBRARY} NAME)
|
||||
|
||||
set(_LIPO_ARGS -remove i386 ${_TBB_LIBRARY_FILENAME} -output ${_TBB_LIBRARY_FILENAME})
|
||||
message(STATUS "${LIPO_COMMAND} ${_LIPO_ARGS}")
|
||||
|
||||
# first we use lipo to remove i386 from each dylib
|
||||
execute_process(
|
||||
COMMAND ${LIPO_COMMAND} ${_LIPO_ARGS}
|
||||
WORKING_DIRECTORY ${_TBB_LIBRARY_DIR}
|
||||
ERROR_VARIABLE _LIPO_ERROR
|
||||
)
|
||||
|
||||
if (_LIPO_ERROR)
|
||||
message(FATAL_ERROR "There was an error removing i386 for ${_TBB_LIBRARY_FILENAME} - ${_LIPO_ERROR}")
|
||||
endif ()
|
||||
|
||||
set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_TBB_LIBRARY} ${_TBB_LIBRARY_FILENAME})
|
||||
|
||||
message(STATUS "${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS}")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# AndroidTBBLibCopy.cmake
|
||||
# TBBLibCopy.cmake
|
||||
# cmake/externals/tbb
|
||||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
|
||||
# first find the so files in the source dir
|
||||
file(GLOB_RECURSE _TBB_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/build/*.so")
|
||||
file(GLOB_RECURSE _TBB_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/build/*.${TBB_LIBS_SUFFIX}")
|
||||
|
||||
# raise an error if we found none
|
||||
if (NOT _TBB_LIBRARIES)
|
31
cmake/externals/vhacd/CMakeLists.txt
vendored
Normal file
31
cmake/externals/vhacd/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
set(EXTERNAL_NAME vhacd)
|
||||
|
||||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/v-hacd-master.zip
|
||||
URL_MD5 3bfc94f8dd3dfbfe8f4dc088f4820b3e
|
||||
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/VHACD_LIB.lib CACHE FILEPATH "Path to V-HACD debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/Release/VHACD_LIB.lib CACHE FILEPATH "Path to V-HACD release library")
|
||||
else ()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to V-HACD debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libVHACD.a CACHE FILEPATH "Path to V-HACD release library")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to V-HACD include directory")
|
40
cmake/macros/AddResourcesToOSXBundle.cmake
Normal file
40
cmake/macros/AddResourcesToOSXBundle.cmake
Normal file
|
@ -0,0 +1,40 @@
|
|||
#
|
||||
# AddResourcesToOSXBundle.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Stephen Birarda on 04/27/15.
|
||||
# Copyright 2015 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
|
||||
#
|
||||
|
||||
macro(_RECURSIVELY_SET_PACKAGE_LOCATION _PATH)
|
||||
# enumerate the items
|
||||
foreach(_ITEM ${ARGN})
|
||||
|
||||
if (IS_DIRECTORY "${_ITEM}")
|
||||
# recurse into the directory and check for more resources
|
||||
file(GLOB _DIR_CONTENTS "${_ITEM}/*")
|
||||
|
||||
# figure out the path for this directory and then call ourselves to recurse into it
|
||||
get_filename_component(_ITEM_PATH ${_ITEM} NAME)
|
||||
_recursively_set_package_location("${_PATH}/${_ITEM_PATH}" ${_DIR_CONTENTS})
|
||||
else ()
|
||||
# add any files in the root to the resources folder directly
|
||||
SET_SOURCE_FILES_PROPERTIES(${_ITEM} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources${_PATH}")
|
||||
list(APPEND DISCOVERED_RESOURCES ${_ITEM})
|
||||
endif ()
|
||||
|
||||
endforeach()
|
||||
endmacro(_RECURSIVELY_SET_PACKAGE_LOCATION _PATH)
|
||||
|
||||
macro(ADD_RESOURCES_TO_OS_X_BUNDLE _RSRC_FOLDER)
|
||||
|
||||
# GLOB the resource directory
|
||||
file(GLOB _ROOT_ITEMS "${_RSRC_FOLDER}/*")
|
||||
|
||||
# recursively enumerate from the root items
|
||||
_recursively_set_package_location("" ${_ROOT_ITEMS})
|
||||
|
||||
endmacro(ADD_RESOURCES_TO_OS_X_BUNDLE _RSRC_FOLDER)
|
|
@ -37,7 +37,7 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE)
|
|||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} --no-libraries $<TARGET_FILE:${TARGET_NAME}>"
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<TARGET_FILE:${TARGET_NAME}>"
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
|
@ -16,6 +16,7 @@ macro(LINK_HIFI_LIBRARIES)
|
|||
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
|
||||
if (NOT TARGET ${HIFI_LIBRARY})
|
||||
add_subdirectory("${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}" "${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}")
|
||||
set_target_properties(${HIFI_LIBRARY} PROPERTIES FOLDER "Libraries")
|
||||
endif ()
|
||||
|
||||
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
|
||||
|
|
|
@ -22,11 +22,11 @@ macro(SETUP_EXTERNALS_BINARY_DIR)
|
|||
endif ()
|
||||
endif ()
|
||||
|
||||
set(EXTERNALS_BINARY_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/build-ext")
|
||||
set(EXTERNALS_BINARY_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/ext")
|
||||
if (ANDROID)
|
||||
set(EXTERNALS_BINARY_DIR "${EXTERNALS_BINARY_ROOT_DIR}/android/${CMAKE_GENERATOR_FOLDER_NAME}")
|
||||
else ()
|
||||
set(EXTERNALS_BINARY_DIR "${EXTERNALS_BINARY_ROOT_DIR}/${CMAKE_GENERATOR_FOLDER_NAME}")
|
||||
endif ()
|
||||
|
||||
endmacro()
|
||||
endmacro()
|
||||
|
|
|
@ -34,4 +34,4 @@ macro(SETUP_HIFI_PROJECT)
|
|||
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
|
||||
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
|
||||
endforeach()
|
||||
endmacro()
|
||||
endmacro()
|
||||
|
|
56
cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake
Normal file
56
cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake
Normal file
|
@ -0,0 +1,56 @@
|
|||
#
|
||||
# CopyDirectoryBesideTarget.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Stephen Birarda on 04/30/15.
|
||||
# Copyright 2015 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
|
||||
#
|
||||
|
||||
macro(SYMLINK_OR_COPY_DIRECTORY_BESIDE_TARGET _SHOULD_SYMLINK _DIRECTORY _DESTINATION)
|
||||
|
||||
# remove the current directory
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory $<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}
|
||||
)
|
||||
|
||||
if (${_SHOULD_SYMLINK})
|
||||
|
||||
# first create the destination
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}
|
||||
)
|
||||
|
||||
# the caller wants a symlink, so just add a command to symlink all contents of _DIRECTORY
|
||||
# in the destination - we can't simply create a symlink for _DESTINATION
|
||||
# because the remove_directory call above would delete the original files
|
||||
|
||||
file(GLOB _DIR_ITEMS ${_DIRECTORY}/*)
|
||||
|
||||
foreach(_ITEM ${_DIR_ITEMS})
|
||||
# get the filename for this item
|
||||
get_filename_component(_ITEM_FILENAME ${_ITEM} NAME)
|
||||
|
||||
# add the command to symlink this item
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E create_symlink
|
||||
${_ITEM}
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}/${_ITEM_FILENAME}
|
||||
)
|
||||
endforeach()
|
||||
else ()
|
||||
# copy the directory
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${_DIRECTORY}
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}
|
||||
)
|
||||
endif ()
|
||||
# glob everything in this directory - add a custom command to copy any files
|
||||
endmacro(SYMLINK_OR_COPY_DIRECTORY_BESIDE_TARGET _SHOULD_SYMLINK _DIRECTORY)
|
|
@ -18,48 +18,12 @@
|
|||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("libovr")
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
if (NOT ANDROID)
|
||||
|
||||
find_path(LIBOVR_INCLUDE_DIRS OVR.h PATH_SUFFIXES Include HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_path(LIBOVR_SRC_DIR Util_Render_Stereo.h PATH_SUFFIXES Src/Util HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
|
||||
if (APPLE)
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES ovr PATH_SUFFIXES Lib/Mac/Debug HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES ovr PATH_SUFFIXES Lib/Mac/Release HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(ApplicationServices ApplicationServices)
|
||||
find_library(IOKit IOKit)
|
||||
elseif (UNIX)
|
||||
find_library(UDEV_LIBRARY_RELEASE udev /usr/lib/x86_64-linux-gnu/)
|
||||
find_library(XINERAMA_LIBRARY_RELEASE Xinerama /usr/lib/x86_64-linux-gnu/)
|
||||
|
||||
if (CMAKE_CL_64)
|
||||
set(LINUX_ARCH_DIR "i386")
|
||||
else()
|
||||
set(LINUX_ARCH_DIR "x86_64")
|
||||
endif()
|
||||
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES ovr PATH_SUFFIXES Lib/Linux/Debug/${LINUX_ARCH_DIR} HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES ovr PATH_SUFFIXES Lib/Linux/Release/${LINUX_ARCH_DIR} HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
|
||||
select_library_configurations(UDEV)
|
||||
select_library_configurations(XINERAMA)
|
||||
|
||||
elseif (WIN32)
|
||||
if (MSVC10)
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
elseif (MSVC12)
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2013 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2013 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
endif ()
|
||||
find_package(ATL)
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LIBOVR DEFAULT_MSG LIBOVR_INCLUDE_DIRS LIBOVR_LIBRARIES)
|
||||
|
||||
else (NOT ANDROID)
|
||||
set(_VRLIB_JNI_DIR "VRLib/jni")
|
||||
set(_VRLIB_LIBS_DIR "VRLib/obj/local/armeabi-v7a")
|
||||
|
@ -76,31 +40,4 @@ else (NOT ANDROID)
|
|||
find_library(TURBOJPEG_LIBRARY NAMES jpeg PATH_SUFFIXES 3rdParty/turbojpeg HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
endif (NOT ANDROID)
|
||||
|
||||
select_library_configurations(LIBOVR)
|
||||
set(LIBOVR_LIBRARIES ${LIBOVR_LIBRARY})
|
||||
|
||||
list(APPEND LIBOVR_ARGS_LIST LIBOVR_INCLUDE_DIRS LIBOVR_SRC_DIR LIBOVR_LIBRARY)
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND LIBOVR_LIBRARIES ${IOKit} ${ApplicationServices})
|
||||
list(APPEND LIBOVR_ARGS_LIST IOKit ApplicationServices)
|
||||
elseif (ANDROID)
|
||||
|
||||
list(APPEND LIBOVR_ANDROID_LIBRARIES "-lGLESv3" "-lEGL" "-landroid" "-lOpenMAXAL" "-llog" "-lz" "-lOpenSLES")
|
||||
list(APPEND LIBOVR_ARGS_LIST LIBOVR_ANDROID_LIBRARIES LIBOVR_VRLIB_DIR MINIZIP_DIR JNI_DIR TURBOJPEG_LIBRARY)
|
||||
elseif (UNIX)
|
||||
list(APPEND LIBOVR_LIBRARIES "${UDEV_LIBRARY}" "${XINERAMA_LIBRARY}")
|
||||
list(APPEND LIBOVR_ARGS_LIST UDEV_LIBRARY XINERAMA_LIBRARY)
|
||||
elseif (WIN32)
|
||||
list(APPEND LIBOVR_LIBRARIES ${ATL_LIBRARIES})
|
||||
list(APPEND LIBOVR_ARGS_LIST ATL_LIBRARIES)
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibOVR DEFAULT_MSG ${LIBOVR_ARGS_LIST})
|
||||
|
||||
if (ANDROID)
|
||||
list(APPEND LIBOVR_INCLUDE_DIRS ${LIBOVR_SRC_DIR} ${MINIZIP_DIR} ${JNI_DIR})
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(LIBOVR_INCLUDE_DIRS LIBOVR_LIBRARIES LIBOVR_SEARCH_DIRS)
|
||||
|
|
21
cmake/modules/FindOpenVR.cmake
Normal file
21
cmake/modules/FindOpenVR.cmake
Normal file
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# FindLibOVR.cmake
|
||||
#
|
||||
# Try to find the LibOVR library to use the Oculus
|
||||
|
||||
# Once done this will define
|
||||
#
|
||||
# OPENVR_FOUND - system found LibOVR
|
||||
# OPENVR_INCLUDE_DIRS - the LibOVR include directory
|
||||
# OPENVR_LIBRARIES - Link this to use LibOVR
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (NOT ANDROID)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OPENVR DEFAULT_MSG OPENVR_INCLUDE_DIRS OPENVR_LIBRARIES)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(OPENVR_INCLUDE_DIRS OPENVR_LIBRARIES OPENVR_SEARCH_DIRS)
|
|
@ -19,40 +19,16 @@
|
|||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("vhacd")
|
||||
|
||||
macro(_FIND_VHACD_LIBRARY _var)
|
||||
set(_${_var}_NAMES ${ARGN})
|
||||
find_library(${_var}_LIBRARY_RELEASE
|
||||
NAMES ${_${_var}_NAMES}
|
||||
HINTS
|
||||
${VHACD_SEARCH_DIRS}
|
||||
$ENV{VHACD_ROOT_DIR}
|
||||
PATH_SUFFIXES lib lib/Release
|
||||
)
|
||||
|
||||
find_library(${_var}_LIBRARY_DEBUG
|
||||
NAMES ${_${_var}_NAMES}
|
||||
HINTS
|
||||
${VHACD_SEARCH_DIRS}
|
||||
$ENV{VHACD_ROOT_DIR}
|
||||
PATH_SUFFIXES lib lib/Debug
|
||||
)
|
||||
|
||||
select_library_configurations(${_var})
|
||||
|
||||
mark_as_advanced(${_var}_LIBRARY)
|
||||
mark_as_advanced(${_var}_LIBRARY)
|
||||
endmacro()
|
||||
|
||||
find_path(VHACD_INCLUDE_DIRS VHACD.h PATH_SUFFIXES include HINTS ${VHACD_SEARCH_DIRS})
|
||||
|
||||
find_path(VHACD_INCLUDE_DIRS VHACD.h PATH_SUFFIXES include HINTS ${VHACD_SEARCH_DIRS} $ENV{VHACD_ROOT_DIR})
|
||||
if(NOT WIN32)
|
||||
_FIND_VHACD_LIBRARY(VHACD libVHACD.a)
|
||||
else()
|
||||
_FIND_VHACD_LIBRARY(VHACD VHACD_LIB)
|
||||
endif()
|
||||
find_library(VHACD_LIBRARY_DEBUG NAMES VHACD VHACD_LIB PATH_SUFFIXES lib/Debug HINTS ${VHACD_SEARCH_DIRS})
|
||||
find_library(VHACD_LIBRARY_RELEASE NAMES VHACD VHACD_LIB PATH_SUFFIXES lib/Release lib HINTS ${VHACD_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(VHACD)
|
||||
|
||||
set(VHACD_LIBRARIES ${VHACD_LIBRARY})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(VHACD "Could NOT find VHACD, try to set the path to VHACD root folder in the system variable VHACD_ROOT_DIR or create a directory vhacd in HIFI_LIB_DIR and paste the necessary files there"
|
||||
VHACD_INCLUDE_DIRS VHACD_LIBRARIES)
|
||||
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
#
|
||||
# FindVisage.cmake
|
||||
#
|
||||
# Try to find the Visage controller library
|
||||
#
|
||||
# You must provide a VISAGE_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# VISAGE_FOUND - system found Visage
|
||||
# VISAGE_INCLUDE_DIRS - the Visage include directory
|
||||
# VISAGE_LIBRARIES - Link this to use Visage
|
||||
#
|
||||
# Created on 2/11/2014 by Andrzej Kapolka
|
||||
# Copyright 2014 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("visage")
|
||||
|
||||
find_path(VISAGE_BASE_INCLUDE_DIR VisageTracker2.h PATH_SUFFIXES include HINTS ${VISAGE_SEARCH_DIRS})
|
||||
|
||||
if (APPLE)
|
||||
find_path(VISAGE_XML_INCLUDE_DIR libxml/xmlreader.h HINTS /usr/include/libxml2 ${VISAGE_SEARCH_DIRS})
|
||||
find_path(VISAGE_OPENCV_INCLUDE_DIR cv.h PATH_SUFFIXES dependencies/OpenCV_MacOSX/include HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_path(VISAGE_OPENCV2_INCLUDE_DIR opencv.hpp PATH_SUFFIXES dependencies/OpenCV_MacOSX/include/opencv2 HINTS ${VISAGE_SEARCH_DIRS})
|
||||
|
||||
find_library(VISAGE_CORE_LIBRARY NAME vscore PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_library(VISAGE_VISION_LIBRARY NAME vsvision PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_library(VISAGE_OPENCV_LIBRARY NAME OpenCV PATH_SUFFIXES dependencies/OpenCV_MacOSX/lib HINTS ${VISAGE_SEARCH_DIRS})
|
||||
|
||||
find_library(CoreVideo CoreVideo)
|
||||
find_library(QTKit QTKit)
|
||||
find_library(IOKit IOKit)
|
||||
|
||||
elseif (WIN32)
|
||||
find_path(VISAGE_XML_INCLUDE_DIR libxml/xmlreader.h PATH_SUFFIXES dependencies/libxml2/include HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_path(VISAGE_OPENCV_INCLUDE_DIR opencv/cv.h PATH_SUFFIXES dependencies/OpenCV/include HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_path(VISAGE_OPENCV2_INCLUDE_DIR cv.h PATH_SUFFIXES dependencies/OpenCV/include/opencv HINTS ${VISAGE_SEARCH_DIRS})
|
||||
|
||||
find_library(VISAGE_CORE_LIBRARY NAME vscore PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_library(VISAGE_VISION_LIBRARY NAME vsvision PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
|
||||
find_library(VISAGE_OPENCV_LIBRARY NAME opencv_core243 PATH_SUFFIXES dependencies/OpenCV/lib HINTS ${VISAGE_SEARCH_DIRS})
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
list(APPEND VISAGE_ARGS_LIST VISAGE_BASE_INCLUDE_DIR VISAGE_XML_INCLUDE_DIR
|
||||
VISAGE_OPENCV_INCLUDE_DIR VISAGE_OPENCV2_INCLUDE_DIR
|
||||
VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY)
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND VISAGE_ARGS_LIST CoreVideo QTKit IOKit)
|
||||
endif ()
|
||||
|
||||
find_package_handle_standard_args(Visage DEFAULT_MSG ${VISAGE_ARGS_LIST})
|
||||
|
||||
set(VISAGE_INCLUDE_DIRS "${VISAGE_XML_INCLUDE_DIR}" "${VISAGE_OPENCV_INCLUDE_DIR}" "${VISAGE_OPENCV2_INCLUDE_DIR}" "${VISAGE_BASE_INCLUDE_DIR}")
|
||||
set(VISAGE_LIBRARIES "${VISAGE_CORE_LIBRARY}" "${VISAGE_VISION_LIBRARY}")
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND VISAGE_LIBRARIES "${CoreVideo}" "${QTKit}" "${IOKit}")
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(VISAGE_INCLUDE_DIRS VISAGE_LIBRARIES)
|
|
@ -1,39 +1,18 @@
|
|||
set(TARGET_NAME domain-server)
|
||||
|
||||
if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32)
|
||||
set(_SHOULD_SYMLINK_RESOURCES TRUE)
|
||||
else ()
|
||||
set(_SHOULD_SYMLINK_RESOURCES FALSE)
|
||||
endif ()
|
||||
|
||||
# setup the project and link required Qt modules
|
||||
setup_hifi_project(Network)
|
||||
|
||||
# remove current resources dir
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
|
||||
)
|
||||
# copy all files in resources, including web
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/resources"
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
|
||||
)
|
||||
|
||||
if (NOT WIN32)
|
||||
# remove the web directory so we can make it a symlink
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web
|
||||
)
|
||||
|
||||
# make the web directory a symlink
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E create_symlink
|
||||
"${PROJECT_SOURCE_DIR}/resources/web"
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web
|
||||
)
|
||||
|
||||
endif ()
|
||||
# TODO: find a solution that will handle web file changes in resources on windows without a re-build.
|
||||
# Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will
|
||||
# not be re-copied. This is worked-around on OS X/UNIX by using a symlink.
|
||||
symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources")
|
||||
|
||||
# link the shared hifi libraries
|
||||
link_hifi_libraries(embedded-webserver networking shared)
|
||||
|
@ -52,4 +31,4 @@ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
|||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
[
|
||||
{
|
||||
"name": "metaverse",
|
||||
"label": "Metaverse Registration",
|
||||
"label": "Metaverse / Networking",
|
||||
"settings": [
|
||||
{
|
||||
"name": "access_token",
|
||||
"label": "Access Token",
|
||||
"help": "This is an access token generated on the <a href='https://metaverse.highfidelity.io/user/security' target='_blank'>My Security</a> page of your High Fidelity account.<br/>Generate a token with the 'domains' scope and paste it here.<br/>This is required to associate this domain-server with a domain in your account."
|
||||
"help": "This is your OAuth access token to connect this domain-server with your High Fidelity account. <br/>It can be generated by clicking the 'Connect Account' button above.<br/>You can also go to the <a href='https://metaverse.highfidelity.com/user/security' target='_blank'>My Security</a> page of your account and generate a token with the 'domains' scope and paste it here.",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -30,7 +31,7 @@
|
|||
},
|
||||
{
|
||||
"value": "disabled",
|
||||
"label": "None: use the network information I have entered for this domain at metaverse.highfidelity.io"
|
||||
"label": "None: use the network information I have entered for this domain at metaverse.highfidelity.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -44,6 +45,30 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Places / Paths",
|
||||
"html_id": "places_paths",
|
||||
"settings": [
|
||||
{
|
||||
"name": "paths",
|
||||
"label": "Paths",
|
||||
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
||||
"type": "table",
|
||||
"key": {
|
||||
"name": "path",
|
||||
"label": "Path",
|
||||
"placeholder": "/"
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"name": "viewpoint",
|
||||
"label": "Viewpoint",
|
||||
"placeholder": "/512,512,512"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "security",
|
||||
"label": "Security",
|
||||
|
@ -74,6 +99,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "maximum_user_capacity",
|
||||
"label": "Maximum User Capacity",
|
||||
"help": "The limit on how many avatars can be connected at once. 0 means no limit.",
|
||||
"placeholder": "0",
|
||||
"default": "0",
|
||||
"advanced": false
|
||||
},
|
||||
{
|
||||
"name": "allowed_editors",
|
||||
"type": "table",
|
||||
|
@ -87,6 +120,13 @@
|
|||
"can_set": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "editors_are_rezzers",
|
||||
"type": "checkbox",
|
||||
"label": "Only editors can create new entities",
|
||||
"help": "When checked, only those who can edit the domain can create new entites.",
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -325,6 +365,24 @@
|
|||
"default": "resources/models.svo",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "persistAsFileType",
|
||||
"label": "File format for entity server's persistent data",
|
||||
"help": "This defines how the entity server will save entities to disk.",
|
||||
"default": "svo",
|
||||
"type": "select",
|
||||
"options": [
|
||||
{
|
||||
"value": "svo",
|
||||
"label": "Entity server persists data as SVO"
|
||||
},
|
||||
{
|
||||
"value": "json",
|
||||
"label": "Entity server persists data as JSON"
|
||||
}
|
||||
],
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "persistInterval",
|
||||
"label": "Save Check Interval",
|
||||
|
@ -453,5 +511,21 @@
|
|||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatar_mixer",
|
||||
"label": "Avatar Mixer",
|
||||
"assignment-types": [1],
|
||||
"settings": [
|
||||
{
|
||||
"name": "max_node_send_bandwidth",
|
||||
"type": "double",
|
||||
"label": "Per-Node Bandwidth",
|
||||
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
||||
"placeholder": 1.0,
|
||||
"default": 1.0,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,21 +1,10 @@
|
|||
body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-lead {
|
||||
color: #66CCCC;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.table-lead .lead-line {
|
||||
background-color: #66CCCC;
|
||||
}
|
||||
|
||||
#queued-lead {
|
||||
color: #F77777;
|
||||
}
|
||||
|
||||
#queued-lead .lead-line {
|
||||
background-color: #F77777;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.table-lead h3 {
|
||||
|
@ -44,14 +33,6 @@ span.port {
|
|||
color: #666666;
|
||||
}
|
||||
|
||||
.stats-key {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.stale {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.locked {
|
||||
color: #428bca;
|
||||
}
|
||||
|
@ -81,7 +62,7 @@ span.port {
|
|||
}
|
||||
|
||||
td.buttons {
|
||||
width: 14px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
td .glyphicon {
|
||||
|
@ -97,7 +78,74 @@ td.reorder-buttons .glyphicon {
|
|||
display: inherit;
|
||||
}
|
||||
|
||||
td a.glyphicon {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
tr.new-row {
|
||||
color: #3c763d;
|
||||
background-color: #dff0d8;
|
||||
}
|
||||
|
||||
.highchart-modal .modal-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#xs-advanced-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#advanced-toggle-button-xs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: 30px auto 0;
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #333;
|
||||
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
/* Prevent first frame from flickering when animation starts */
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0.0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
} 40% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
body.stop-scrolling {
|
||||
height: 100%;
|
||||
overflow: hidden; }
|
||||
|
||||
.sweet-overlay {
|
||||
background-color: black;
|
||||
/* IE8 */
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
|
||||
/* IE8 */
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
@ -6,11 +14,11 @@
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
display: none;
|
||||
z-index: 1000; }
|
||||
z-index: 10000; }
|
||||
|
||||
.sweet-alert {
|
||||
background-color: white;
|
||||
font-family: sans-serif;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
width: 478px;
|
||||
padding: 17px;
|
||||
border-radius: 5px;
|
||||
|
@ -22,7 +30,7 @@
|
|||
margin-top: -200px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
z-index: 2000; }
|
||||
z-index: 99999; }
|
||||
@media all and (max-width: 540px) {
|
||||
.sweet-alert {
|
||||
width: auto;
|
||||
|
@ -36,15 +44,120 @@
|
|||
text-align: center;
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
position: relative; }
|
||||
position: relative;
|
||||
margin: 25px 0;
|
||||
padding: 0;
|
||||
line-height: 40px;
|
||||
display: block; }
|
||||
.sweet-alert p {
|
||||
color: #797979;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
text-align: inherit;
|
||||
float: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: normal; }
|
||||
.sweet-alert fieldset {
|
||||
border: none;
|
||||
position: relative; }
|
||||
.sweet-alert .sa-error-container {
|
||||
background-color: #f1f1f1;
|
||||
margin-left: -17px;
|
||||
margin-right: -17px;
|
||||
overflow: hidden;
|
||||
padding: 0 10px;
|
||||
max-height: 0;
|
||||
webkit-transition: padding 0.15s, max-height 0.15s;
|
||||
transition: padding 0.15s, max-height 0.15s; }
|
||||
.sweet-alert .sa-error-container.show {
|
||||
padding: 10px 0;
|
||||
max-height: 100px;
|
||||
webkit-transition: padding 0.2s, max-height 0.2s;
|
||||
transition: padding 0.25s, max-height 0.25s; }
|
||||
.sweet-alert .sa-error-container .icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #ea7d7d;
|
||||
color: white;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
margin-right: 3px; }
|
||||
.sweet-alert .sa-error-container p {
|
||||
display: inline-block; }
|
||||
.sweet-alert .sa-input-error {
|
||||
position: absolute;
|
||||
top: 29px;
|
||||
right: 26px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
-webkit-transition: all 0.1s;
|
||||
transition: all 0.1s; }
|
||||
.sweet-alert .sa-input-error::before, .sweet-alert .sa-input-error::after {
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 6px;
|
||||
background-color: #f06e57;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
left: 50%;
|
||||
margin-left: -9px; }
|
||||
.sweet-alert .sa-input-error::before {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg); }
|
||||
.sweet-alert .sa-input-error::after {
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.sweet-alert .sa-input-error.show {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
.sweet-alert input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #d7d7d7;
|
||||
height: 43px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 17px;
|
||||
font-size: 18px;
|
||||
box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.06);
|
||||
padding: 0 12px;
|
||||
display: none;
|
||||
-webkit-transition: all 0.3s;
|
||||
transition: all 0.3s; }
|
||||
.sweet-alert input:focus {
|
||||
outline: none;
|
||||
box-shadow: 0px 0px 3px #c4e6f5;
|
||||
border: 1px solid #b4dbed; }
|
||||
.sweet-alert input:focus::-moz-placeholder {
|
||||
transition: opacity 0.3s 0.03s ease;
|
||||
opacity: 0.5; }
|
||||
.sweet-alert input:focus:-ms-input-placeholder {
|
||||
transition: opacity 0.3s 0.03s ease;
|
||||
opacity: 0.5; }
|
||||
.sweet-alert input:focus::-webkit-input-placeholder {
|
||||
transition: opacity 0.3s 0.03s ease;
|
||||
opacity: 0.5; }
|
||||
.sweet-alert input::-moz-placeholder {
|
||||
color: #bdbdbd; }
|
||||
.sweet-alert input:-ms-input-placeholder {
|
||||
color: #bdbdbd; }
|
||||
.sweet-alert input::-webkit-input-placeholder {
|
||||
color: #bdbdbd; }
|
||||
.sweet-alert.show-input input {
|
||||
display: block; }
|
||||
.sweet-alert button {
|
||||
background-color: #AEDEF4;
|
||||
color: white;
|
||||
|
@ -52,6 +165,7 @@
|
|||
box-shadow: none;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 5px;
|
||||
padding: 10px 32px;
|
||||
margin: 26px 5px 0 5px;
|
||||
|
@ -71,22 +185,29 @@
|
|||
background-color: #b6b6b6; }
|
||||
.sweet-alert button.cancel:focus {
|
||||
box-shadow: rgba(197, 205, 211, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.0470588) 0px 0px 0px 1px inset !important; }
|
||||
.sweet-alert button::-moz-focus-inner {
|
||||
border: 0; }
|
||||
.sweet-alert[data-has-cancel-button=false] button {
|
||||
box-shadow: none !important; }
|
||||
.sweet-alert .icon {
|
||||
.sweet-alert[data-has-confirm-button=false][data-has-cancel-button=false] {
|
||||
padding-bottom: 40px; }
|
||||
.sweet-alert .sa-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 4px solid gray;
|
||||
-webkit-border-radius: 40px;
|
||||
border-radius: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 20px auto;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
box-sizing: content-box; }
|
||||
.sweet-alert .icon.error {
|
||||
.sweet-alert .sa-icon.sa-error {
|
||||
border-color: #F27474; }
|
||||
.sweet-alert .icon.error .x-mark {
|
||||
.sweet-alert .sa-icon.sa-error .sa-x-mark {
|
||||
position: relative;
|
||||
display: block; }
|
||||
.sweet-alert .icon.error .line {
|
||||
.sweet-alert .sa-icon.sa-error .sa-line {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
width: 47px;
|
||||
|
@ -94,37 +215,39 @@
|
|||
display: block;
|
||||
top: 37px;
|
||||
border-radius: 2px; }
|
||||
.sweet-alert .icon.error .line.left {
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-left {
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
left: 17px; }
|
||||
.sweet-alert .icon.error .line.right {
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-right {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
right: 16px; }
|
||||
.sweet-alert .icon.warning {
|
||||
.sweet-alert .sa-icon.sa-warning {
|
||||
border-color: #F8BB86; }
|
||||
.sweet-alert .icon.warning .body {
|
||||
.sweet-alert .sa-icon.sa-warning .sa-body {
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 47px;
|
||||
left: 50%;
|
||||
top: 10px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
margin-left: -2px;
|
||||
background-color: #F8BB86; }
|
||||
.sweet-alert .icon.warning .dot {
|
||||
.sweet-alert .sa-icon.sa-warning .sa-dot {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
margin-left: -3px;
|
||||
left: 50%;
|
||||
bottom: 10px;
|
||||
background-color: #F8BB86; }
|
||||
.sweet-alert .icon.info {
|
||||
.sweet-alert .sa-icon.sa-info {
|
||||
border-color: #C9DAE1; }
|
||||
.sweet-alert .icon.info::before {
|
||||
.sweet-alert .sa-icon.sa-info::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
|
@ -134,7 +257,7 @@
|
|||
border-radius: 2px;
|
||||
margin-left: -2px;
|
||||
background-color: #C9DAE1; }
|
||||
.sweet-alert .icon.info::after {
|
||||
.sweet-alert .sa-icon.sa-info::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
|
@ -143,17 +266,21 @@
|
|||
margin-left: -3px;
|
||||
top: 19px;
|
||||
background-color: #C9DAE1; }
|
||||
.sweet-alert .icon.success {
|
||||
.sweet-alert .sa-icon.sa-success {
|
||||
border-color: #A5DC86; }
|
||||
.sweet-alert .icon.success::before, .sweet-alert .icon.success::after {
|
||||
.sweet-alert .sa-icon.sa-success::before, .sweet-alert .sa-icon.sa-success::after {
|
||||
content: '';
|
||||
-webkit-border-radius: 40px;
|
||||
border-radius: 40px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 120px;
|
||||
background: white;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.sweet-alert .icon.success::before {
|
||||
.sweet-alert .sa-icon.sa-success::before {
|
||||
-webkit-border-radius: 120px 0 0 120px;
|
||||
border-radius: 120px 0 0 120px;
|
||||
top: -7px;
|
||||
left: -33px;
|
||||
|
@ -161,7 +288,8 @@
|
|||
transform: rotate(-45deg);
|
||||
-webkit-transform-origin: 60px 60px;
|
||||
transform-origin: 60px 60px; }
|
||||
.sweet-alert .icon.success::after {
|
||||
.sweet-alert .sa-icon.sa-success::after {
|
||||
-webkit-border-radius: 0 120px 120px 0;
|
||||
border-radius: 0 120px 120px 0;
|
||||
top: -11px;
|
||||
left: 30px;
|
||||
|
@ -169,17 +297,19 @@
|
|||
transform: rotate(-45deg);
|
||||
-webkit-transform-origin: 0px 60px;
|
||||
transform-origin: 0px 60px; }
|
||||
.sweet-alert .icon.success .placeholder {
|
||||
.sweet-alert .sa-icon.sa-success .sa-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 4px solid rgba(165, 220, 134, 0.2);
|
||||
-webkit-border-radius: 40px;
|
||||
border-radius: 40px;
|
||||
border-radius: 50%;
|
||||
box-sizing: content-box;
|
||||
position: absolute;
|
||||
left: -4px;
|
||||
top: -4px;
|
||||
z-index: 2; }
|
||||
.sweet-alert .icon.success .fix {
|
||||
.sweet-alert .sa-icon.sa-success .sa-fix {
|
||||
width: 5px;
|
||||
height: 90px;
|
||||
background-color: white;
|
||||
|
@ -189,26 +319,26 @@
|
|||
z-index: 1;
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg); }
|
||||
.sweet-alert .icon.success .line {
|
||||
.sweet-alert .sa-icon.sa-success .sa-line {
|
||||
height: 5px;
|
||||
background-color: #A5DC86;
|
||||
display: block;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 2; }
|
||||
.sweet-alert .icon.success .line.tip {
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-tip {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 46px;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.sweet-alert .icon.success .line.long {
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-long {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px;
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg); }
|
||||
.sweet-alert .icon.custom {
|
||||
.sweet-alert .sa-icon.sa-custom {
|
||||
background-size: contain;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
|
@ -222,238 +352,274 @@
|
|||
0% {
|
||||
transform: scale(0.7);
|
||||
-webkit-transform: scale(0.7); }
|
||||
|
||||
45% {
|
||||
transform: scale(1.05);
|
||||
-webkit-transform: scale(1.05); }
|
||||
|
||||
80% {
|
||||
transform: scale(0.95);
|
||||
-webkit-tranform: scale(0.95); }
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); } }
|
||||
@-moz-keyframes showSweetAlert {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
-webkit-transform: scale(0.7); }
|
||||
45% {
|
||||
transform: scale(1.05);
|
||||
-webkit-transform: scale(1.05); }
|
||||
80% {
|
||||
transform: scale(0.95);
|
||||
-webkit-tranform: scale(0.95); }
|
||||
-webkit-transform: scale(0.95); }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); } }
|
||||
|
||||
@keyframes showSweetAlert {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
-webkit-transform: scale(0.7); }
|
||||
|
||||
45% {
|
||||
transform: scale(1.05);
|
||||
-webkit-transform: scale(1.05); }
|
||||
|
||||
80% {
|
||||
transform: scale(0.95);
|
||||
-webkit-tranform: scale(0.95); }
|
||||
-webkit-transform: scale(0.95); }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); } }
|
||||
|
||||
@-webkit-keyframes hideSweetAlert {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); }
|
||||
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
-webkit-transform: scale(0.5); } }
|
||||
@-moz-keyframes hideSweetAlert {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); }
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
-webkit-transform: scale(0.5); } }
|
||||
|
||||
@keyframes hideSweetAlert {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); }
|
||||
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
-webkit-transform: scale(0.5); } }
|
||||
.showSweetAlert {
|
||||
-webkit-animation: showSweetAlert 0.3s;
|
||||
-moz-animation: showSweetAlert 0.3s;
|
||||
animation: showSweetAlert 0.3s; }
|
||||
|
||||
.hideSweetAlert {
|
||||
@-webkit-keyframes slideFromTop {
|
||||
0% {
|
||||
top: 0%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@keyframes slideFromTop {
|
||||
0% {
|
||||
top: 0%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@-webkit-keyframes slideToTop {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 0%; } }
|
||||
|
||||
@keyframes slideToTop {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 0%; } }
|
||||
|
||||
@-webkit-keyframes slideFromBottom {
|
||||
0% {
|
||||
top: 70%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@keyframes slideFromBottom {
|
||||
0% {
|
||||
top: 70%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@-webkit-keyframes slideToBottom {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 70%; } }
|
||||
|
||||
@keyframes slideToBottom {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 70%; } }
|
||||
|
||||
.showSweetAlert[data-animation=pop] {
|
||||
-webkit-animation: showSweetAlert 0.3s;
|
||||
animation: showSweetAlert 0.3s; }
|
||||
.showSweetAlert[data-animation=none] {
|
||||
-webkit-animation: none;
|
||||
animation: none; }
|
||||
.showSweetAlert[data-animation=slide-from-top] {
|
||||
-webkit-animation: slideFromTop 0.3s;
|
||||
animation: slideFromTop 0.3s; }
|
||||
.showSweetAlert[data-animation=slide-from-bottom] {
|
||||
-webkit-animation: slideFromBottom 0.3s;
|
||||
animation: slideFromBottom 0.3s; }
|
||||
|
||||
.hideSweetAlert[data-animation=pop] {
|
||||
-webkit-animation: hideSweetAlert 0.2s;
|
||||
-moz-animation: hideSweetAlert 0.2s;
|
||||
animation: hideSweetAlert 0.2s; }
|
||||
.hideSweetAlert[data-animation=none] {
|
||||
-webkit-animation: none;
|
||||
animation: none; }
|
||||
.hideSweetAlert[data-animation=slide-from-top] {
|
||||
-webkit-animation: slideToTop 0.4s;
|
||||
animation: slideToTop 0.4s; }
|
||||
.hideSweetAlert[data-animation=slide-from-bottom] {
|
||||
-webkit-animation: slideToBottom 0.3s;
|
||||
animation: slideToBottom 0.3s; }
|
||||
|
||||
@-webkit-keyframes animateSuccessTip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px; }
|
||||
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px; }
|
||||
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px; } }
|
||||
@-moz-keyframes animateSuccessTip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px; }
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px; }
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px; } }
|
||||
|
||||
@keyframes animateSuccessTip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px; }
|
||||
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px; }
|
||||
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px; } }
|
||||
|
||||
@-webkit-keyframes animateSuccessLong {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px; }
|
||||
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px; } }
|
||||
@-moz-keyframes animateSuccessLong {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px; }
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px; } }
|
||||
|
||||
@keyframes animateSuccessLong {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px; }
|
||||
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px; } }
|
||||
|
||||
@-webkit-keyframes rotatePlaceholder {
|
||||
0% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
5% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
12% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); }
|
||||
|
||||
100% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); } }
|
||||
@-moz-keyframes rotatePlaceholder {
|
||||
0% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
5% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
12% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); }
|
||||
100% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); } }
|
||||
|
||||
@keyframes rotatePlaceholder {
|
||||
0% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
5% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
12% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); }
|
||||
|
||||
100% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); } }
|
||||
|
||||
.animateSuccessTip {
|
||||
-webkit-animation: animateSuccessTip 0.75s;
|
||||
-moz-animation: animateSuccessTip 0.75s;
|
||||
animation: animateSuccessTip 0.75s; }
|
||||
|
||||
.animateSuccessLong {
|
||||
-webkit-animation: animateSuccessLong 0.75s;
|
||||
-moz-animation: animateSuccessLong 0.75s;
|
||||
animation: animateSuccessLong 0.75s; }
|
||||
|
||||
.icon.success.animate::after {
|
||||
.sa-icon.sa-success.animate::after {
|
||||
-webkit-animation: rotatePlaceholder 4.25s ease-in;
|
||||
-moz-animation: rotatePlaceholder 4.25s ease-in;
|
||||
animation: rotatePlaceholder 4.25s ease-in; }
|
||||
|
||||
@-webkit-keyframes animateErrorIcon {
|
||||
|
@ -461,31 +627,25 @@
|
|||
transform: rotateX(100deg);
|
||||
-webkit-transform: rotateX(100deg);
|
||||
opacity: 0; }
|
||||
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
-webkit-transform: rotateX(0deg);
|
||||
opacity: 1; } }
|
||||
@-moz-keyframes animateErrorIcon {
|
||||
0% {
|
||||
transform: rotateX(100deg);
|
||||
-webkit-transform: rotateX(100deg);
|
||||
opacity: 0; }
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
-webkit-transform: rotateX(0deg);
|
||||
opacity: 1; } }
|
||||
|
||||
@keyframes animateErrorIcon {
|
||||
0% {
|
||||
transform: rotateX(100deg);
|
||||
-webkit-transform: rotateX(100deg);
|
||||
opacity: 0; }
|
||||
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
-webkit-transform: rotateX(0deg);
|
||||
opacity: 1; } }
|
||||
|
||||
.animateErrorIcon {
|
||||
-webkit-animation: animateErrorIcon 0.5s;
|
||||
-moz-animation: animateErrorIcon 0.5s;
|
||||
animation: animateErrorIcon 0.5s; }
|
||||
|
||||
@-webkit-keyframes animateXMark {
|
||||
|
@ -494,108 +654,104 @@
|
|||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
50% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
80% {
|
||||
transform: scale(1.15);
|
||||
-webkit-transform: scale(1.15);
|
||||
margin-top: -6px; }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
margin-top: 0;
|
||||
opacity: 1; } }
|
||||
@-moz-keyframes animateXMark {
|
||||
0% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
50% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
80% {
|
||||
transform: scale(1.15);
|
||||
-webkit-transform: scale(1.15);
|
||||
margin-top: -6px; }
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
margin-top: 0;
|
||||
opacity: 1; } }
|
||||
|
||||
@keyframes animateXMark {
|
||||
0% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
50% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
80% {
|
||||
transform: scale(1.15);
|
||||
-webkit-transform: scale(1.15);
|
||||
margin-top: -6px; }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
margin-top: 0;
|
||||
opacity: 1; } }
|
||||
|
||||
.animateXMark {
|
||||
-webkit-animation: animateXMark 0.5s;
|
||||
-moz-animation: animateXMark 0.5s;
|
||||
animation: animateXMark 0.5s; }
|
||||
|
||||
/*@include keyframes(simpleRotate) {
|
||||
0% { transform: rotateY(0deg); }
|
||||
100% { transform: rotateY(-360deg); }
|
||||
}
|
||||
.simpleRotate {
|
||||
@include animation('simpleRotate 0.75s');
|
||||
}*/
|
||||
@-webkit-keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
border-color: #F8BB86; } }
|
||||
@-moz-keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #F8D486; }
|
||||
100% {
|
||||
border-color: #F8BB86; } }
|
||||
|
||||
@keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
border-color: #F8BB86; } }
|
||||
|
||||
.pulseWarning {
|
||||
-webkit-animation: pulseWarning 0.75s infinite alternate;
|
||||
-moz-animation: pulseWarning 0.75s infinite alternate;
|
||||
animation: pulseWarning 0.75s infinite alternate; }
|
||||
|
||||
@-webkit-keyframes pulseWarningIns {
|
||||
0% {
|
||||
background-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
background-color: #F8BB86; } }
|
||||
@-moz-keyframes pulseWarningIns {
|
||||
0% {
|
||||
background-color: #F8D486; }
|
||||
100% {
|
||||
background-color: #F8BB86; } }
|
||||
|
||||
@keyframes pulseWarningIns {
|
||||
0% {
|
||||
background-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
background-color: #F8BB86; } }
|
||||
|
||||
.pulseWarningIns {
|
||||
-webkit-animation: pulseWarningIns 0.75s infinite alternate;
|
||||
-moz-animation: pulseWarningIns 0.75s infinite alternate;
|
||||
animation: pulseWarningIns 0.75s infinite alternate; }
|
||||
|
||||
/* Internet Explorer 9 has some special quirks that are fixed here */
|
||||
/* The icons are not animated. */
|
||||
/* This file is automatically merged into sweet-alert.min.js through Gulp */
|
||||
/* Error icon */
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-left {
|
||||
-ms-transform: rotate(45deg) \9; }
|
||||
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-right {
|
||||
-ms-transform: rotate(-45deg) \9; }
|
||||
|
||||
/* Success icon */
|
||||
.sweet-alert .sa-icon.sa-success {
|
||||
border-color: transparent\9; }
|
||||
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-tip {
|
||||
-ms-transform: rotate(45deg) \9; }
|
||||
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-long {
|
||||
-ms-transform: rotate(-45deg) \9; }
|
|
@ -1,4 +1,4 @@
|
|||
</div>
|
||||
<script src='/js/jquery.min.js'></script>
|
||||
<script src='/js/bootstrap.min.js'></script>
|
||||
<script src='/js/domain-server.js'></script>
|
||||
<script src='/js/domain-server.js'></script>
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
<head>
|
||||
<title>domain-server</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Bootstrap -->
|
||||
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/style.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/sweet-alert.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
|
||||
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
|
@ -21,7 +22,7 @@
|
|||
</button>
|
||||
<a class="navbar-brand" href="/">domain-server</a>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
|
@ -37,4 +38,4 @@
|
|||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid">
|
||||
|
|
File diff suppressed because one or more lines are too long
5
domain-server/resources/web/js/query-string.js
Normal file
5
domain-server/resources/web/js/query-string.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
function qs(key) {
|
||||
key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
|
||||
var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,33 +0,0 @@
|
|||
$(document).ready(function(){
|
||||
/*
|
||||
* Clamped-width.
|
||||
* Usage:
|
||||
* <div data-clampedwidth=".myParent">This long content will force clamped width</div>
|
||||
*
|
||||
* Author: LV
|
||||
*/
|
||||
|
||||
$('[data-clampedwidth]').each(function () {
|
||||
var elem = $(this);
|
||||
var parentPanel = elem.data('clampedwidth');
|
||||
var resizeFn = function () {
|
||||
var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth'));
|
||||
elem.css('width', sideBarNavWidth);
|
||||
};
|
||||
|
||||
resizeFn();
|
||||
$(window).resize(resizeFn);
|
||||
});
|
||||
|
||||
|
||||
var listSource = $('#list-group-template').html();
|
||||
var listTemplate = _.template(listSource);
|
||||
|
||||
reloadSettings();
|
||||
|
||||
function reloadSettings() {
|
||||
$.getJSON('describe-setup.json', function(data){
|
||||
$('.list-group').html(listTemplate(data));
|
||||
});
|
||||
}
|
||||
});
|
File diff suppressed because one or more lines are too long
1
domain-server/resources/web/js/sweetalert.min.js
vendored
Executable file
1
domain-server/resources/web/js/sweetalert.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1
domain-server/resources/web/js/underscore-keypath.min.js
vendored
Normal file
1
domain-server/resources/web/js/underscore-keypath.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
!function(){function t(t,r,e,n){return(0!==n?".":"")+r.replace(/\./g,"«dot»")}function r(r){"use strict";if("object"==typeof r&&"Array"===r.constructor.name)return Array.prototype.slice.call(r);if("string"==typeof r){r=r.replace(/\.\./g,"«dot»"),r=r.replace(/\['(([^']|\\')*)'\]/g,t),r=r.replace(/\["(([^"]|\\")*)"\]/g,t),r=r.replace(/([$A-Za-z_][0-9A-Za-z_$]*)\(\)/,"$1");var e=r.split(".");return e=s(e).map(function(t){return t.replace(/«dot»/g,".")})}throw new Error("keypath must be an array or a string")}function e(t){"use strict";return t=null===t?"":String(t),t.charAt(0).toUpperCase()+t.slice(1)}function n(t,r){"use strict";var e=y[r];return e?e(t):void 0}function u(t,r,e){"use strict";if(!(t instanceof Array))return void 0;var n=l[r];return n?n(t,e):void 0}function i(t,r){"use strict";if(""===r)return t;if(0===r.indexOf("@"))return n(t,r);var u=t["get"+e(r)];return void 0===u&&(u=t["is"+e(r)]),void 0===u&&(u=t[r]),"function"==typeof u&&(u=u.call(t)),u}function o(t,r){"use strict";return""===r?!0:"function"==typeof t["get"+e(r)]||"function"==typeof t["is"+e(r)]||s.has(t,r)}function a(t,r,n){"use strict";var i=t["set"+e(r)];return i?i.call(t,n):0===r.indexOf("@")?u(t,r,n):(t[r]=n,n)}function f(t,r){"use strict";var e,n,u=!0;for(e in r)if(r.hasOwnProperty(e)&&(n=r[e],s(t).valueForKeyPath(e)!==n)){u=!1;break}return u}var s;"undefined"!=typeof _&&(s=_);var c,y,l,p,d,h=this;if(c="undefined"!=typeof module&&"undefined"!=typeof require,"undefined"==typeof s&&(s=c?require("underscore"):h.underscore),"undefined"==typeof s)throw new Error("underscore.js is not found!");y={"@first":function(t){return s.first(t)},"@last":function(t){return s.last(t)},"@max":function(t){return s.max(t)},"@min":function(t){return s.min(t)},"@size":function(t){return s.size(t)}},l={"@first":function(t,r){return t[0]=r,r},"@last":function(t,r){return t[Math.max(t.length-1,0)]=r,r}},p={valueForKeyPath:function(t,e,n){"use strict";var u,o,a,f=t;if(null===t||void 0===t)return n;for(u=r(e),o=u.shift(),a=void 0;null!==f&&void 0!==f&&"string"==typeof o;)a=f,f=i(f,o),"function"==typeof f&&(f=f.call(a)),o=u.shift();return null===f||void 0===f?n:f},setValueForKeyPath:function(t,e,n){"use strict";var u,i,o=r(e);return u=o.pop(),i=s(t).valueForKeyPath(o.join(".")),null!==i&&void 0!==i?a(i,u,n):void 0},pluckByKeyPath:function(t,r){"use strict";var e,n,u=[];for(e=0;e<t.length;e+=1)n=t[e],u[e]=s(n).valueForKeyPath(r);return u},whereByKeyPath:function(t,r){"use strict";var e,n,u=[];for(e=0;e<t.length;e+=1)n=t[e],f(n,r)&&u.push(n);return u},findWhereByKeyPath:function(t,r){"use strict";var e,n;for(e=0;e<t.length;e+=1)if(n=t[e],f(n,r))return n;return null},sortByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.sortBy(t,r):s.sortBy(t,function(t){return s.valueForKeyPath(t,r)})},groupByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.groupBy(t,r):s.groupBy(t,function(t){return s.valueForKeyPath(t,r)})},indexByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.indexBy(t,r):s.indexBy(t,function(t){return s.valueForKeyPath(t,r)})},countByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.countBy(t,r):s.countBy(t,function(t){return s.valueForKeyPath(t,r)})},hasKeyPath:function(t,e){"use strict";var n,u,i;return null===t||void 0===t?!1:(n=r(e),u=n.pop(),i=this.valueForKeyPath(t,n),null!==i&&void 0!==i?o(i,u):!1)}},d={getValueForKeyPath:p.valueForKeyPath},s.mixin(p),s.mixin(d),c&&(module.exports=s,module.exports.__debug__={toSegments:r})}();
|
File diff suppressed because one or more lines are too long
|
@ -1,68 +1,82 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col" class="hidden-xs" data-spy="affix" data-offset-top="55">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<li>
|
||||
<a href="#<%-group.name %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
<div class="panel panel-default <%- (isAdvanced) ? 'advanced-setting' : '' %>" id="<%- group.name %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<%= getFormGroup(group.name, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<%= getFormGroup(group.name, setting, values, true,
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" class="hidden-xs" data-spy="affix" data-offset-top="55" data-clampedwidth="#setup-sidebar-col">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% panelID = group.name ? group.name : group.label %>
|
||||
<li>
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<div id="xs-advanced-container" class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button id="advanced-toggle-button-xs" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12">
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<div id="panels"></div>
|
||||
|
||||
</form>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button class="btn btn-success save-button" id="small-save-button">Save and restart</button>
|
||||
</div>
|
||||
|
@ -83,8 +97,9 @@
|
|||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='/js/sweet-alert.min.js'></script>
|
||||
<script src='/js/sweetalert.min.js'></script>
|
||||
<script src='/js/settings.js'></script>
|
||||
<script src='/js/form2js.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
115
domain-server/resources/web/stats/css/json.human.css
Executable file
115
domain-server/resources/web/stats/css/json.human.css
Executable file
|
@ -0,0 +1,115 @@
|
|||
.jh-root, .jh-type-object, .jh-type-array, .jh-key, .jh-value, .jh-root tr {
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jh-key, .jh-value {
|
||||
margin: 0;
|
||||
padding: 0.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jh-value {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.jh-type-number {
|
||||
text-align: center;
|
||||
color: #5286BC;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jh-type-bool-true {
|
||||
text-align: center;
|
||||
color: #5A811C;
|
||||
}
|
||||
|
||||
.jh-type-bool-false {
|
||||
text-align: center;
|
||||
color: #D45317;
|
||||
}
|
||||
|
||||
.jh-type-bool-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 5px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.jh-type-string {
|
||||
font-style: italic;
|
||||
color: #6E6E6E;
|
||||
}
|
||||
|
||||
.jh-array-key {
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jh-object-key, .jh-array-key {
|
||||
color: #444;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jh-type-object tr:nth-child(odd), .jh-type-array tr:nth-child(odd) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.jh-type-object tr:nth-child(even), .jh-type-array tr:nth-child(even) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.jh-type-object, .jh-type-array {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.jh-root {
|
||||
border: 1px solid #ccc;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
th.jh-key {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th.jh-key.stale {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.jh-type-object tr, .jh-type-array tr {
|
||||
border: 1px solid #ddd;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.jh-type-object tr:last-child, .jh-type-array tr:last-child {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.jh-type-object tr:hover, .jh-type-array tr:hover {
|
||||
border: 1px solid #F99927;
|
||||
}
|
||||
|
||||
.jh-empty {
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.jh-a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jh-a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jh-a span.jh-type-string {
|
||||
text-decoration: none;
|
||||
color : #268ddd;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
@ -1,6 +1,15 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
<div id="stats-lead" class="table-lead"><h3>Stats</h3><div class="lead-line"></div></div>
|
||||
<table id="stats-table" class="table"><tbody></tbody></table>
|
||||
<div class="col-xs-12 table-lead" id="stats-lead"><h3>Stats</h3><div class="lead-line"></div></div>
|
||||
<div class="col-xs-12">
|
||||
<p class="help"><em>Click on any of the numerical values in the tables below to view a line graph of the incoming values.</em></p>
|
||||
</div>
|
||||
<div class="col-xs-12" id="stats-container"></div>
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/query-string.js'></script>
|
||||
<script src='js/stats.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
<script src='js/json.human.js'></script>
|
||||
<script src='js/highcharts-custom.js'></script>
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
247
domain-server/resources/web/stats/js/highcharts-custom.js
Normal file
247
domain-server/resources/web/stats/js/highcharts-custom.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
Highcharts 4.0.4 JS v/Highstock 2.0.4 (2014-09-02)
|
||||
|
||||
(c) 2009-2014 Torstein Honsi
|
||||
|
||||
License: www.highcharts.com/license
|
||||
*/
|
||||
(function(){function v(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function G(){var a,b=arguments,c,d={},e=function(a,b){var c,d;"object"!==typeof a&&(a={});for(d in b)b.hasOwnProperty(d)&&((c=b[d])&&"object"===typeof c&&"[object Array]"!==Object.prototype.toString.call(c)&&"renderTo"!==d&&"number"!==typeof c.nodeType?a[d]=e(a[d]||{},c):a[d]=b[d]);return a};!0===b[0]&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a<c;a++)d=e(d,b[a]);return d}function E(a,b){return parseInt(a,
|
||||
b||10)}function pa(a){return"string"===typeof a}function da(a){return a&&"object"===typeof a}function ta(a){return"[object Array]"===Object.prototype.toString.call(a)}function ga(a){return"number"===typeof a}function ab(a){return R.log(a)/R.LN10}function ia(a){return R.pow(10,a)}function qa(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function w(a){return a!==u&&null!==a}function N(a,b,c){var d,e;if(pa(b))w(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(w(b)&&
|
||||
da(b))for(d in b)a.setAttribute(d,b[d]);return e}function ea(a){return ta(a)?a:[a]}function n(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],c!==u&&null!==c)return c}function J(a,b){ra&&!Z&&b&&b.opacity!==u&&(b.filter="alpha(opacity="+100*b.opacity+")");v(a.style,b)}function ja(a,b,c,d,e){a=y.createElement(a);b&&v(a,b);e&&J(a,{padding:0,border:"none",margin:0});c&&J(a,c);d&&d.appendChild(a);return a}function nb(a,b){var c=function(){return u};c.prototype=new a;v(c.prototype,b);return c}
|
||||
function ka(a,b,c,d){var e=U.numberFormat,f=M.lang,g=+a||0,h=-1===b?(g.toString().split(".")[1]||"").length:isNaN(b=X(b))?2:b,k=void 0===c?f.decimalPoint:c,f=void 0===d?f.thousandsSep:d,l=0>g?"-":"",m=String(E(g=X(g).toFixed(h))),q=3<m.length?m.length%3:0;return e!==ka?e(a,b,c,d):l+(q?m.substr(0,q)+f:"")+m.substr(q).replace(/(\d{3})(?=\d)/g,"$1"+f)+(h?k+X(g-m).toFixed(h).slice(2):"")}function sa(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function bb(a,b,c){var d=a[b];a[b]=function(){var a=
|
||||
Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,a)}}function ua(a,b){for(var c="{",d=!1,e,f,g,h,k,l=[];-1!==(c=a.indexOf(c));){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");k=g.length;e=b;for(h=0;h<k;h++)e=e[g[h]];f.length&&(f=f.join(":"),g=/\.([0-9])/,h=M.lang,k=void 0,/f$/.test(f)?(k=(k=f.match(g))?k[1]:-1,null!==e&&(e=ka(e,k,h.decimalPoint,-1<f.indexOf(",")?h.thousandsSep:""))):e=Ka(f,e))}l.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}l.push(a);return l.join("")}
|
||||
function ob(a,b,c,d){var e;c=n(c,1);e=a/c;b||(b=[1,2,2.5,5,10],!1===d&&(1===c?b=[1,2,5,10]:.1>=c&&(b=[1/c])));for(d=0;d<b.length&&!(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2);d++);return a*c}function cb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return 0===d?a.ss_i-c.ss_i:d});for(e=0;e<c;e++)delete a[e].ss_i}function La(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function va(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Ma(a,b){for(var c in a)a[c]&&
|
||||
a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Na(a){Oa||(Oa=ja("div"));a&&Oa.appendChild(a);Oa.innerHTML=""}function ha(a){return parseFloat(a.toPrecision(14))}function pb(){var a=M.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";la=M.global.Date||window.Date;wa=6E4*(a&&M.global.timezoneOffset||0);Pa=a?la.UTC:function(a,b,c,g,h,k){return(new la(a,b,n(c,1),n(g,0),n(h,0),n(k,0))).getTime()};db=b+"Minutes";eb=b+"Hours";fb=b+"Day";Da=b+"Date";Qa=b+"Month";Ra=b+"FullYear";qb=c+"Minutes";
|
||||
rb=c+"Hours";gb=c+"Date";sb=c+"Month";tb=c+"FullYear"}function O(){}function xa(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;c||d||this.addLabel()}function ma(){this.init.apply(this,arguments)}function Ea(){this.init.apply(this,arguments)}var u,y=document,I=window,R=Math,A=R.round,K=R.floor,ya=R.ceil,x=R.max,T=R.min,X=R.abs,Fa=R.cos,Sa=R.sin,ub=R.PI,vb=2*ub/360,na=navigator.userAgent,Bb=I.opera,ra=/msie/i.test(na)&&!Bb,Cb=/AppleWebKit/.test(na),Ta=/Firefox/.test(na),wb=/(Mobile|Android|Windows Phone)/.test(na),
|
||||
Z=!!y.createElementNS&&!!y.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect,Db=Ta&&4>parseInt(na.split("Firefox/")[1],10),ca=!Z&&!ra&&!!y.createElement("canvas").getContext,Ga,xb={},hb=0,Oa,M,Ka,S,ib,z,aa,Eb=function(){return u},Y=[],Ha=0,Fb=/^[0-9]+$/,la,Pa,wa,db,eb,fb,Da,Qa,Ra,qb,rb,gb,sb,tb,P={},U;I.Highcharts?aa(16,!0):U=I.Highcharts={};Ka=function(a,b,c){if(!w(b)||isNaN(b))return"Invalid date";a=n(a,"%Y-%m-%d %H:%M:%S");var d=new la(b-wa),e,f=d[eb](),g=d[fb](),h=d[Da](),k=d[Qa](),
|
||||
l=d[Ra](),m=M.lang,q=m.weekdays,d=v({a:q[g].substr(0,3),A:q[g],d:sa(h),e:h,b:m.shortMonths[k],B:m.months[k],m:sa(k+1),y:l.toString().substr(2,2),Y:l,H:sa(f),I:sa(f%12||12),l:f%12||12,M:sa(d[db]()),p:12>f?"AM":"PM",P:12>f?"am":"pm",S:sa(d.getSeconds()),L:sa(A(b%1E3),3)},U.dateFormats);for(e in d)for(;-1!==a.indexOf("%"+e);)a=a.replace("%"+e,"function"===typeof d[e]?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};aa=function(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+
|
||||
a;if(b)throw c;I.console&&console.log(c)};z={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5,month:26784E5,year:31556952E3};ib={init:function(a,b,c){b=b||"";var d=a.shift,e=-1<b.indexOf("C"),f=e?7:3,g;b=b.split(" ");c=[].concat(c);var h,k,l=function(a){for(g=a.length;g--;)"M"===a[g]&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(l(b),l(c));a.isArea&&(h=b.splice(b.length-6,6),k=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);
|
||||
a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(k));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(1===c)e=d;else if(f===b.length&&1>c)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};(function(a){I.HighchartsAdapter=I.HighchartsAdapter||a&&{init:function(b){var c=a.fx;a.extend(a.easing,{easeOutQuad:function(a,b,c,g,h){return-g*(b/=
|
||||
h)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(b,e){var f=c.step,g;"cur"===e?f=c.prototype:"_default"===e&&a.Tween&&(f=a.Tween.propHooks[e],e="set");(g=f[e])&&(f[e]=function(a){var c;a=b?a:this;if("align"!==a.prop)return c=a.elem,c.attr?c.attr(a.prop,"cur"===e?u:a.now):g.apply(this,arguments)})});bb(a.cssHooks.opacity,"get",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});this.addAnimSetter("d",function(a){var c=a.elem,f;a.started||(f=b.init(c,c.d,c.toD),
|
||||
a.start=f[0],a.end=f[1],a.started=!0);c.attr("d",b.step(a.start,a.end,a.pos,c.toD))});this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){var c,g=a.length;for(c=0;c<g;c++)if(!1===b.call(a[c],a[c],c,a))return c};a.fn.highcharts=function(){var a="Chart",b=arguments,c,g;this[0]&&(pa(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,1)),c=b[0],c!==u&&(c.chart=c.chart||{},c.chart.renderTo=this[0],new U[a](c,b[1]),g=this),c===u&&(g=Y[N(this[0],"data-highcharts-chart")]));
|
||||
return g}},addAnimSetter:function(b,c){a.Tween?a.Tween.propHooks[b]={set:c}:a.fx.step[b]=c},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=y.removeEventListener?"removeEventListener":"detachEvent";y[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,
|
||||
d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g="detached"+c,h;!ra&&d&&(delete d.layerX,delete d.layerY,delete d.returnValue);v(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each(["preventDefault","stopPropagation"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){"preventDefault"===b&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);!e||f.isDefaultPrevented()||h||e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;c.pageX===u&&(c.pageX=a.pageX,c.pageY=a.pageY);return c},animate:function(b,
|
||||
c,d){var e=a(b);b.style||(b.style={});c.d&&(b.toD=c.d,c.d=1);e.stop();c.opacity!==u&&b.attr&&(c.opacity+="px");b.hasAnim=1;e.animate(c,d)},stop:function(b){b.hasAnim&&a(b).stop()}}})(I.jQuery);var Ua=I.HighchartsAdapter,ba=Ua||{};Ua&&Ua.init.call(Ua,ib);var Va=ba.adapterRun,Ia=ba.inArray,t=ba.each,jb=ba.grep,Gb=ba.offset,kb=ba.map,F=ba.addEvent,L=ba.removeEvent,B=ba.fireEvent,Hb=ba.washMouseEvent,Wa=ba.animate,Xa=ba.stop,lb={enabled:!0,x:0,y:15,style:{color:"#606060",cursor:"default",fontSize:"11px"}};
|
||||
M={colors:"#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #8085e8 #8d4653 #91e8e1".split(" "),symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January February March April May June July August September October November December".split(" "),shortMonths:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),weekdays:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),decimalPoint:".",numericSymbols:"kMGTPE".split(""),
|
||||
resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,canvasToolsURL:"http://code.highcharts.com@product.cdnpath@//Highstock 2.0.4/modules/canvas-tools.js",VMLRadialGradientURL:"http://code.highcharts.com@product.cdnpath@//Highstock 2.0.4/gfx/vml-radial-gradient.png"},chart:{borderColor:"#4572A7",borderRadius:0,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacing:[10,10,15,10],backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",resetZoomButton:{theme:{zIndex:20},
|
||||
position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",margin:15,style:{color:"#333333",fontSize:"18px"}},subtitle:{text:"",align:"center",style:{color:"#555555"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{enabled:!0,lineWidthPlus:1,radiusPlus:2},select:{fillColor:"#FFFFFF",lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:G(lb,{align:"center",
|
||||
enabled:!1,formatter:function(){return null===this.y?"":ka(this.y,-1)},verticalAlign:"bottom",y:0}),cropThreshold:300,pointRange:0,states:{hover:{lineWidthPlus:1,marker:{},halo:{size:10,opacity:.25}},select:{marker:{}}},stickyTracking:!0,turboThreshold:1E3}},labels:{style:{position:"absolute",color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderColor:"#909090",borderRadius:0,navigation:{activeColor:"#274b6d",inactiveColor:"#CCC"},
|
||||
shadow:!1,itemStyle:{color:"#333333",fontSize:"12px",fontWeight:"bold"},itemHoverStyle:{color:"#000"},itemHiddenStyle:{color:"#CCC"},itemCheckboxStyle:{position:"absolute",width:"13px",height:"13px"},symbolPadding:5,verticalAlign:"bottom",x:0,y:0,title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold",position:"relative",top:"45%"},style:{position:"absolute",backgroundColor:"white",opacity:.5,textAlign:"center"}},tooltip:{enabled:!0,animation:Z,backgroundColor:"rgba(249, 249, 249, .85)",
|
||||
borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},headerFormat:'<span style="font-size: 10px">{point.key}</span><br/>',pointFormat:'<span style="color:{series.color}">\u25cf</span> {series.name}: <b>{point.y}</b><br/>',shadow:!0,snap:wb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",
|
||||
whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var Ya=M.plotOptions;pb();var Ib=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Jb=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Kb=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,za=function(a){var b=[],c,d;(function(a){a&&
|
||||
a.stops?d=kb(a.stops,function(a){return za(a[1])}):(c=Ib.exec(a))?b=[E(c[1]),E(c[2]),E(c[3]),parseFloat(c[4],10)]:(c=Jb.exec(a))?b=[E(c[1],16),E(c[2],16),E(c[3],16),1]:(c=Kb.exec(a))&&(b=[E(c[1]),E(c[2]),E(c[3]),1])})(a);return{get:function(c){var f;d?(f=G(a),f.stops=[].concat(f.stops),t(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?"rgb"===c?"rgb("+b[0]+","+b[1]+","+b[2]+")":"a"===c?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)t(d,function(b){b.brighten(a)});
|
||||
else if(ga(a)&&0!==a){var c;for(c=0;3>c;c++)b[c]+=E(255*a),0>b[c]&&(b[c]=0),255<b[c]&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};O.prototype={opacity:1,textProps:"fontSize fontWeight fontFamily color lineHeight width textDecoration textShadow HcTextStroke".split(" "),init:function(a,b){this.element="span"===b?ja(b):y.createElementNS("http://www.w3.org/2000/svg",b);this.renderer=a},animate:function(a,b,c){b=n(b,S,!0);Xa(this);b?(b=G(b,{}),c&&(b.complete=c),Wa(this,
|
||||
a,b)):(this.attr(a),c&&c());return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,k,l,m,q,r,p,n=[];a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];h=d.gradients;l=a.stops;r=c.radialReference;ta(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});"radialGradient"===f&&r&&!w(g.gradientUnits)&&(g=G(g,{cx:r[0]-r[2]/2+g.cx*r[2],cy:r[1]-r[2]/2+g.cy*r[2],r:g.r*r[2],gradientUnits:"userSpaceOnUse"}));for(p in g)"id"!==p&&n.push(p,
|
||||
g[p]);for(p in l)n.push(l[p]);n=n.join(",");h[n]?a=h[n].attr("id"):(g.id=a="highcharts-"+hb++,h[n]=k=d.createElement(f).attr(g).add(d.defs),k.stops=[],t(l,function(a){0===a[1].indexOf("rgba")?(e=za(a[1]),m=e.get("rgb"),q=e.get("a")):(m=a[1],q=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":m,"stop-opacity":q}).add(k);k.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;"string"===typeof a&&b!==u&&(c=a,a={},a[c]=b);if("string"===
|
||||
typeof a)g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a)d=a[c],h=!1,this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(f||(this.symbolAttr(a),f=!0),h=!0),!this.rotation||"x"!==c&&"y"!==c||(this.doTransform=!0),h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e),this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d);this.doTransform&&(this.updateTransform(),this.doTransform=!1)}return g},
|
||||
updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,"height"===a?x(b-(c[d].cutHeight||0),0):"d"===a?this.d:b)},addClass:function(a){var b=this.element,c=N(b,"class")||"";-1===c.indexOf(a)&&N(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;t("x y r start end width height innerR anchorX anchorY".split(" "),function(c){b[c]=n(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",
|
||||
a?"url("+this.renderer.url+"#"+a.id+")":"none")},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||0;d=A(e)%2/2;a.x=K(a.x||this.x||0)+d;a.y=K(a.y||this.y||0)+d;a.width=K((a.width||this.width||0)-2*d);a.height=K((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;a&&a.color&&(a.fill=a.color);if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&
|
||||
a.width&&"text"===d.nodeName.toLowerCase()&&E(a.width);b&&(a=v(b,c));this.styles=a;e&&(ca||!Z&&this.renderer.forExport)&&delete a.width;if(ra&&!Z)J(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";N(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;Ga&&"click"===a?(d.ontouchstart=function(a){c.touchEventFired=la.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(-1===
|
||||
na.indexOf("Android")||1100<la.now()-(c.touchEventFired||0))&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));
|
||||
a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(w(c)||w(d))&&a.push("scale("+n(c,1)+" "+n(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||pa(c))this.alignTo=d=c||"renderer",qa(f,
|
||||
this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=n(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if("right"===d||"center"===d)f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=A(f);if("bottom"===e||"middle"===e)g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=A(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,
|
||||
b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*vb;d=this.textStr;var h;if(""===d||Fb.test(d))h="num."+d.toString().length+(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if("http://www.w3.org/2000/svg"===c.namespaceURI||b.forExport){try{a=c.getBBox?v({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(k){}if(!a||0>a.width)a={width:0,height:0}}else a=this.htmlGetBBox();b.isSVG&&(c=a.width,d=a.height,ra&&f&&"11px"===f.fontSize&&"16.9"===d.toPrecision(3)&&
|
||||
(a.height=d=14),e&&(a.width=X(d*Sa(g))+X(c*Fa(g)),a.height=X(d*Fa(g))+X(c*Sa(g))));this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){a&&"http://www.w3.org/2000/svg"===this.element.namespaceURI?this.element.removeAttribute("visibility"):this.attr({visibility:a?"inherit":"visible"});return this},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer,
|
||||
c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;a&&(this.parentGroup=a);this.parentInverted=a&&a.inverted;void 0!==this.textStr&&b.buildText(this);f&&(c.handleZ=!0,f=E(f));if(c.handleZ)for(a=d.childNodes,g=0;g<a.length;g++)if(b=a[g],c=N(b,"zIndex"),b!==e&&(E(c)>f||!w(f)&&w(c))){d.insertBefore(e,b);h=!0;break}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||
|
||||
{},c=a.shadows,d=a.renderer.isSVG&&"SPAN"===b.nodeName&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Xa(a);a.clipPath&&(a.clipPath=a.clipPath.destroy());if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();a.stops=null}a.safeRemoveChild(b);for(c&&t(c,function(b){a.safeRemoveChild(b)});d&&d.div&&0===d.div.childNodes.length;)b=d.parentGroup,a.safeRemoveChild(d.div),delete d.div,d=b;a.alignTo&&qa(a.renderer.alignedObjects,a);for(e in a)delete a[e];
|
||||
return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,k,l,m;if(a){k=n(a.width,3);l=(a.opacity||.15)/k;m=this.parentInverted?"(-1,-1)":"("+n(a.offsetX,1)+", "+n(a.offsetY,1)+")";for(e=1;e<=k;e++)f=g.cloneNode(0),h=2*k+1-2*e,N(f,{isShadow:"true",stroke:a.color||"black","stroke-opacity":l*e,"stroke-width":h,transform:"translate"+m,fill:"none"}),c&&(N(f,"height",x(N(f,"height")-h,0)),f.cutHeight=h),b?b.element.appendChild(f):g.parentNode.insertBefore(f,g),d.push(f);this.shadows=d}return this},
|
||||
xGetter:function(a){"circle"===this.element.nodeName&&(a={x:"cx",y:"cy"}[a]||a);return this._defaultGetter(a)},_defaultGetter:function(a){a=n(this[a],this.element?this.element.getAttribute(a):null,0);/^[\-0-9\.]+$/.test(a)&&(a=parseFloat(a));return a},dSetter:function(a,b,c){a&&a.join&&(a=a.join(" "));/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");c.setAttribute(b,a);this[b]=a},dashstyleSetter:function(a){var b;if(a=a&&a.toLowerCase()){a=a.replace("shortdashdotdot","3,1,1,1,1,1,").replace("shortdashdot","3,1,1,1").replace("shortdot",
|
||||
"1,1,").replace("shortdash","3,1,").replace("longdash","8,3,").replace(/dot/g,"1,3,").replace("dash","4,3,").replace(/,$/,"").split(",");for(b=a.length;b--;)a[b]=E(a[b])*this["stroke-width"];a=a.join(",").replace("NaN","none");this.element.setAttribute("stroke-dasharray",a)}},alignSetter:function(a){this.element.setAttribute("text-anchor",{left:"start",center:"middle",right:"end"}[a])},opacitySetter:function(a,b,c){this[b]=a;c.setAttribute(b,a)},titleSetter:function(a){var b=this.element.getElementsByTagName("title")[0];
|
||||
b||(b=y.createElementNS("http://www.w3.org/2000/svg","title"),this.element.appendChild(b));b.textContent=n(a,"").replace(/<[^>]*>/g,"")},textSetter:function(a){a!==this.textStr&&(delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this))},fillSetter:function(a,b,c){"string"===typeof a?c.setAttribute(b,a):a&&this.colorGradient(a,b,c)},zIndexSetter:function(a,b,c){c.setAttribute(b,a);this[b]=a},_defaultSetter:function(a,b,c){c.setAttribute(b,a)}};O.prototype.yGetter=O.prototype.xGetter;
|
||||
O.prototype.translateXSetter=O.prototype.translateYSetter=O.prototype.rotationSetter=O.prototype.verticalAlignSetter=O.prototype.scaleXSetter=O.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};O.prototype["stroke-widthSetter"]=O.prototype.strokeSetter=function(a,b,c){this[b]=a;this.stroke&&this["stroke-width"]?(this.strokeWidth=this["stroke-width"],O.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0):"stroke-width"===
|
||||
b&&0===a&&this.hasStroke&&(c.removeAttribute("stroke"),this.hasStroke=!1)};var Ja=function(){this.init.apply(this,arguments)};Ja.prototype={Element:O,init:function(a,b,c,d,e){var f=location,g;d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));g=d.element;a.appendChild(g);-1===a.innerHTML.indexOf("xmlns")&&N(g,"xmlns","http://www.w3.org/2000/svg");this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(Ta||Cb)&&y.getElementsByTagName("base").length?f.href.replace(/#.*?$/,
|
||||
"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(y.createTextNode("Created with Highcharts 4.0.4 /Highstock 2.0.4"));this.defs=this.createElement("defs").add();this.forExport=e;this.gradients={};this.cache={};this.setSize(b,c,!1);var h;Ta&&a.getBoundingClientRect&&(this.subPixelFix=b=function(){J(a,{left:0,top:0});h=a.getBoundingClientRect();J(a,{left:ya(h.left)-h.left+"px",top:ya(h.top)-h.top+"px"})},b(),F(I,"resize",b))},getStyle:function(a){return this.style=
|
||||
v({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ma(this.gradients||{});this.gradients=null;a&&(this.defs=a.destroy());this.subPixelFix&&L(I,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},
|
||||
buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=n(a.textStr,"").toString(),f=-1!==e.indexOf("<"),g=b.childNodes,h,k,l=N(b,"x"),m=a.styles,q=a.textWidth,r=m&&m.lineHeight,p=m&&m.HcTextStroke,Q=g.length,D=function(a){return r?E(r):c.fontMetrics(/(px|em)$/.test(a&&a.style.fontSize)?a.style.fontSize:m&&m.fontSize||c.style.fontSize||12,a).h};Q--;)b.removeChild(g[Q]);f||p||-1!==e.indexOf(" ")?(h=/<.*style="([^"]+)".*>/,k=/<.*href="(http[^"]+)".*>/,q&&!a.added&&this.box.appendChild(b),e=
|
||||
f?e.replace(/<(b|strong)>/g,'<span style="font-weight:bold">').replace(/<(i|em)>/g,'<span style="font-style:italic">').replace(/<a/g,"<span").replace(/<\/(b|strong|i|em|a)>/g,"</span>").split(/<br.*?>/g):[e],""===e[e.length-1]&&e.pop(),t(e,function(e,f){var g,r=0;e=e.replace(/<span/g,"|||<span").replace(/<\/span>/g,"</span>|||");g=e.split("|||");t(g,function(e){if(""!==e||1===g.length){var p={},n=y.createElementNS("http://www.w3.org/2000/svg","tspan"),Q;h.test(e)&&(Q=e.match(h)[1].replace(/(;| |^)color([ :])/,
|
||||
"$1fill$2"),N(n,"style",Q));k.test(e)&&!d&&(N(n,"onclick",'location.href="'+e.match(k)[1]+'"'),J(n,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(" "!==e){n.appendChild(y.createTextNode(e));r?p.dx=0:f&&null!==l&&(p.x=l);N(n,p);b.appendChild(n);!r&&f&&(!Z&&d&&J(n,{display:"block"}),N(n,"dy",D(n)));if(q){e=e.replace(/([^\^])-/g,"$1- ").split(" ");for(var p=1<g.length||1<e.length&&"nowrap"!==m.whiteSpace,t,C,H=m.HcHeight,u=[],w=D(n),yb=1;p&&(e.length||
|
||||
u.length);)delete a.bBox,t=a.getBBox(),C=t.width,!Z&&c.forExport&&(C=c.measureSpanWidth(n.firstChild.data,a.styles)),(t=C>q)&&1!==e.length?(n.removeChild(n.firstChild),u.unshift(e.pop())):(e=u,u=[],e.length&&(yb++,H&&yb*w>H?(e=["..."],a.attr("title",a.textStr)):(n=y.createElementNS("http://www.w3.org/2000/svg","tspan"),N(n,{dy:w,x:l}),Q&&N(n,"style",Q),b.appendChild(n))),C>q&&(q=C)),e.length&&n.appendChild(y.createTextNode(e.join(" ").replace(/- /g,"-")))}r++}}})})):b.appendChild(y.createTextNode(e))},
|
||||
button:function(a,b,c,d,e,f,g,h,k){var l=this.label(a,b,c,k,null,null,null,null,"button"),m=0,q,r,p,n,D,t;a={x1:0,y1:0,x2:0,y2:1};e=G({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);p=e.style;delete e.style;f=G(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);n=f.style;delete f.style;g=G(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);D=g.style;delete g.style;
|
||||
h=G(e,{style:{color:"#CCC"}},h);t=h.style;delete h.style;F(l.element,ra?"mouseover":"mouseenter",function(){3!==m&&l.attr(f).css(n)});F(l.element,ra?"mouseout":"mouseleave",function(){3!==m&&(q=[e,f,g][m],r=[p,n,D][m],l.attr(q).css(r))});l.setState=function(a){(l.state=m=a)?2===a?l.attr(g).css(D):3===a&&l.attr(h).css(t):l.attr(e).css(p)};return l.on("click",function(){3!==m&&d.call(l)}).attr(e).css(v({cursor:"default"},p))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=A(a[1])-b%2/2);a[2]===a[5]&&
|
||||
(a[2]=a[5]=A(a[2])+b%2/2);return a},path:function(a){var b={fill:"none"};ta(a)?b.d=a:da(a)&&v(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=da(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)};return b.attr(a)},arc:function(a,b,c,d,e,f){da(a)&&(b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x);a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||
|
||||
0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=da(a)?a.r:e;var g=this.createElement("rect");a=da(a)?a:a===u?{}:{x:a,y:b,width:x(c,0),height:x(d,0)};f!==u&&(a.strokeWidth=f,a=g.crisp(a));e&&(a.r=e);g.rSetter=function(a){N(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[n(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return w(a)?b.attr({"class":"highcharts-"+
|
||||
a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:"none"};1<arguments.length&&v(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(A(b),A(c),d,e,f),k=/^url\((.*?)\)$/,l,m;h?(g=this.path(h),v(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&v(g,f)):k.test(a)&&(m=function(a,b){a.element&&
|
||||
(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(A((d-b[0])/2),A((e-b[1])/2)))},l=a.match(k)[1],a=xb[l]||f&&f.width&&f.height&&[f.width,f.height],g=this.image(l).attr({x:b,y:c}),g.isImg=!0,a?m(g,a):(g.attr({width:0,height:0}),ja("img",{onload:function(){m(g,xb[l]=[this.width,this.height])},src:l})));return g},symbols:{circle:function(a,b,c,d){var e=.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",
|
||||
a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start;c=e.r||c||d;var g=e.end-.001;d=e.innerR;var h=e.open,k=Fa(f),l=Sa(f),m=Fa(g),g=Sa(g);e=e.end-f<ub?0:1;return["M",a+c*k,b+c*l,"A",c,c,0,e,1,a+c*m,b+c*g,h?"M":"L",a+d*m,b+d*g,"A",d,d,0,e,0,a+d*k,b+d*l,h?"":"Z"]},
|
||||
callout:function(a,b,c,d,e){var f=T(e&&e.r||0,c,d),g=f+6,h=e&&e.anchorX,k=e&&e.anchorY;e=A(e.strokeWidth||0)%2/2;a+=e;b+=e;e=["M",a+f,b,"L",a+c-f,b,"C",a+c,b,a+c,b,a+c,b+f,"L",a+c,b+d-f,"C",a+c,b+d,a+c,b+d,a+c-f,b+d,"L",a+f,b+d,"C",a,b+d,a,b+d,a,b+d-f,"L",a,b+f,"C",a,b,a,b,a+f,b];h&&h>c&&k>b+g&&k<b+d-g?e.splice(13,3,"L",a+c,k-6,a+c+6,k,a+c,k+6,a+c,b+d-f):h&&0>h&&k>b+g&&k<b+d-g?e.splice(33,3,"L",a,k+6,a-6,k,a,k-6,a,b+f):k&&k>d&&h>a+g&&h<a+c-g?e.splice(23,3,"L",h+6,b+d,h,b+d+6,h-6,b+d,a+f,b+d):k&&0>
|
||||
k&&h>a+g&&h<a+c-g&&e.splice(3,3,"L",h-6,b,h,b-6,h+6,b,c-f,b);return e}},clipRect:function(a,b,c,d){var e="highcharts-"+hb++,f=this.createElement("clipPath").attr({id:e}).add(this.defs);a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},text:function(a,b,c,d){var e=ca||!Z&&this.forExport,f={};if(d&&!this.forExport)return this.html(a,b,c);f.x=Math.round(b||0);c&&(f.y=Math.round(c));if(a||0===a)f.text=a;a=this.createElement("text").attr(f);e&&a.css({position:"absolute"});d||(a.xSetter=function(a,
|
||||
b,c){var d=c.getElementsByTagName("tspan"),e,f=c.getAttribute(b),r;for(r=0;r<d.length;r++)e=d[r],e.getAttribute(b)===f&&e.setAttribute(b,a);c.setAttribute(b,a)});return a},fontMetrics:function(a,b){a=a||this.style.fontSize;b&&I.getComputedStyle&&(b=b.element||b,a=I.getComputedStyle(b,"").fontSize);a=/px/.test(a)?E(a):/em/.test(a)?12*parseFloat(a):12;var c=24>a?a+4:A(1.2*a),d=A(.8*c);return{h:c,b:d,f:a}},label:function(a,b,c,d,e,f,g,h,k){function l(){var a,b;a=n.element.style;H=(void 0===Aa||void 0===
|
||||
z||p.styles.textAlign)&&n.textStr&&n.getBBox();p.width=(Aa||H.width||0)+2*W+x;p.height=(z||H.height||0)+2*W;oa=W+r.fontMetrics(a&&a.fontSize,n).b;E&&(D||(a=A(-V*W),b=h?-oa:0,p.box=D=d?r.symbol(d,a,b,p.width,p.height,C):r.rect(a,b,p.width,p.height,0,C["stroke-width"]),D.attr("fill","none").add(p)),D.isImg||D.attr(v({width:A(p.width),height:A(p.height)},C)),C=null)}function m(){var a=p.styles,a=a&&a.textAlign,b=x+W*(1-V),c;c=h?0:oa;w(Aa)&&H&&("center"===a||"right"===a)&&(b+={center:.5,right:1}[a]*(Aa-
|
||||
H.width));if(b!==n.x||c!==n.y)n.attr("x",b),c!==u&&n.attr("y",c);n.x=b;n.y=c}function q(a,b){D?D.attr(a,b):C[a]=b}var r=this,p=r.g(k),n=r.text("",0,0,g).attr({zIndex:1}),D,H,V=0,W=3,x=0,Aa,z,mb,y,B=0,C={},oa,E;p.onAdd=function(){n.add(p);p.attr({text:a||0===a?a:"",x:b,y:c});D&&w(e)&&p.attr({anchorX:e,anchorY:f})};p.widthSetter=function(a){Aa=a};p.heightSetter=function(a){z=a};p.paddingSetter=function(a){w(a)&&a!==W&&(W=a,m())};p.paddingLeftSetter=function(a){w(a)&&a!==x&&(x=a,m())};p.alignSetter=
|
||||
function(a){V={left:0,center:.5,right:1}[a]};p.textSetter=function(a){a!==u&&n.textSetter(a);l();m()};p["stroke-widthSetter"]=function(a,b){a&&(E=!0);B=a%2/2;q(b,a)};p.strokeSetter=p.fillSetter=p.rSetter=function(a,b){"fill"===b&&a&&(E=!0);q(b,a)};p.anchorXSetter=function(a,b){e=a;q(b,a+B-mb)};p.anchorYSetter=function(a,b){f=a;q(b,a-y)};p.xSetter=function(a){p.x=a;V&&(a-=V*((Aa||H.width)+W));mb=A(a);p.attr("translateX",mb)};p.ySetter=function(a){y=p.y=A(a);p.attr("translateY",y)};var F=p.css;return v(p,
|
||||
{css:function(a){if(a){var b={};a=G(a);t(p.textProps,function(c){a[c]!==u&&(b[c]=a[c],delete a[c])});n.css(b)}return F.call(p,a)},getBBox:function(){return{width:H.width+2*W,height:H.height+2*W,x:H.x-W,y:H.y-W}},shadow:function(a){D&&D.shadow(a);return p},destroy:function(){L(p.element,"mouseenter");L(p.element,"mouseleave");n&&(n=n.destroy());D&&(D=D.destroy());O.prototype.destroy.call(p);p=r=l=m=q=null}})}};xa.prototype={addLabel:function(){var a=this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,
|
||||
f=a.names,g=this.pos,h=b.labels,k=h.rotation,l=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/l.length||!d&&(c.margin[3]||.33*c.chartWidth),m=g===l[0],q=g===l[l.length-1],r,f=e?n(e[g],f[g],g):g,e=this.label,p=l.info;a.isDatetimeAxis&&p&&(r=b.dateTimeLabelFormats[p.higherRanks[g]||p.unitName]);this.isFirst=m;this.isLast=q;b=a.labelFormatter.call({axis:a,chart:c,isFirst:m,isLast:q,dateTimeLabelFormat:r,value:a.isLog?ha(ia(f)):f});g=d&&{width:x(1,A(d-2*(h.padding||10)))+"px"};
|
||||
w(e)?e&&e.attr({text:b}).css(g):(r={align:a.labelAlign},ga(k)&&(r.rotation=k),d&&h.ellipsis&&(g.HcHeight=a.len/l.length),this.label=e=w(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(r).css(v(g,h.style)).add(a.labelGroup):null,a.tickBaseline=c.renderer.fontMetrics(h.style.fontSize,e).b,k&&2===a.side&&(a.tickBaseline*=Fa(k*vb)));this.yOffset=e?n(h.y,a.tickBaseline+(2===a.side?8:-(e.getBBox().height/2))):0},getLabelSize:function(){var a=this.label,b=this.axis;return a?a.getBBox()[b.horiz?"height":
|
||||
"width"]:0},getLabelSides:function(){var a=this.label.getBBox(),b=this.axis,c=b.horiz,d=b.options.labels,a=c?a.width:a.height,b=c?d.x-a*{left:0,center:.5,right:1}[b.labelAlign]:0;return[b,c?a+b:a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=this.isFirst,f=this.isLast,g=d.horiz?b.x:b.y,h=d.reversed,k=d.tickPositions,l=this.getLabelSides(),m=l[0],l=l[1],q,r,p,n=this.label.line;q=n||0;r=d.labelEdge;p=d.justifyLabels&&(e||f);r[q]===u||g+m>r[q]?r[q]=g+l:p||(c=!1);if(p){q=(r=d.justifyToPlot)?d.pos:
|
||||
0;r=r?q+d.len:d.chart.chartWidth;do a+=e?1:-1,p=d.ticks[k[a]];while(k[a]&&(!p||!p.label||p.label.line!==n));d=p&&p.label.xy&&p.label.xy.x+p.getLabelSides()[e?0:1];e&&!h||f&&h?g+m<q&&(g=q-m,p&&g+l>d&&(c=!1)):g+l>r&&(g=r-l,p&&g+m<d&&(c=!1));b.x=g}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-
|
||||
(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var k=this.axis,l=k.transA,m=k.reversed,q=k.staggerLines;a=a+e.x-(f&&d?f*l*(m?-1:1):0);b=b+this.yOffset-(f&&!d?f*l*(m?1:-1):0);q&&(c.line=g/(h||1)%q,b+=k.labelOffset/q*c.line);return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,k=this.label,l=
|
||||
this.pos,m=e.labels,q=this.gridLine,r=h?h+"Grid":"grid",p=h?h+"Tick":"tick",t=e[r+"LineWidth"],D=e[r+"LineColor"],H=e[r+"LineDashStyle"],V=e[p+"Length"],r=e[p+"Width"]||0,w=e[p+"Color"],x=e[p+"Position"],p=this.mark,v=m.step,A=!0,z=d.tickmarkOffset,y=this.getPosition(g,l,z,b),B=y.x,y=y.y,C=g&&B===d.pos+d.len||!g&&y===d.pos?-1:1;c=n(c,1);this.isActive=!0;if(t&&(l=d.getPlotLinePath(l+z,t*C,b,!0),q===u&&(q={stroke:D,"stroke-width":t},H&&(q.dashstyle=H),h||(q.zIndex=1),b&&(q.opacity=0),this.gridLine=
|
||||
q=t?f.path(l).attr(q).add(d.gridGroup):null),!b&&q&&l))q[this.isNew?"attr":"animate"]({d:l,opacity:c});r&&V&&("inside"===x&&(V=-V),d.opposite&&(V=-V),h=this.getMarkPath(B,y,V,r*C,g,f),p?p.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:w,"stroke-width":r,opacity:c}).add(d.axisGroup));k&&!isNaN(B)&&(k.xy=y=this.getLabelPosition(B,y,k,g,m,z,a,v),this.isFirst&&!this.isLast&&!n(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!n(e.showLastLabel,1)?A=!1:d.isRadial||m.step||m.rotation||b||0===
|
||||
c||(A=this.handleOverflow(a,y)),v&&a%v&&(A=!1),A&&!isNaN(y.y)?(y.opacity=c,k[this.isNew?"attr":"animate"](y),this.isNew=!1):k.attr("y",-9999))},destroy:function(){Ma(this,this.axis)}};ma.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:lb,lineColor:"#C0D0E0",lineWidth:1,minPadding:.01,maxPadding:.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,
|
||||
minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:.05,minPadding:.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,
|
||||
formatter:function(){return ka(this.total,-1)},style:lb.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:
|
||||
this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=!1!==d.zoomEnabled;this.categories=d.categories||"category"===e;this.names=[];this.isLog="logarithmic"===e;this.isDatetimeAxis="datetime"===e;this.isLinked=w(d.linkedTo);this.tickmarkOffset=this.categories&&"between"===d.tickmarkPlacement&&1===n(d.tickInterval,1)?.5:0;this.ticks=
|
||||
{};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=n(d.crosshair,ea(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;-1===Ia(this,a.axes)&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||
|
||||
[];a.inverted&&c&&this.reversed===u&&(this.reversed=!0);this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)F(this,f,d[f]);this.isLog&&(this.val2lin=ab,this.lin2val=ia)},setOptions:function(a){this.options=G(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],G(M[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,
|
||||
c=a.categories,d=this.dateTimeLabelFormat,e=M.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=ua(h,this);else if(c)g=b;else if(d)g=Ka(d,b);else if(f&&1E3<=a)for(;f--&&g===u;)c=Math.pow(1E3,f+1),a>=c&&null!==e[f]&&(g=ka(b/c,-1)+e[f]);g===u&&(g=1E4<=X(b)?ka(b,0):ka(b,-1,u,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.ignoreMinPadding=a.ignoreMaxPadding=null;a.buildStacks&&a.buildStacks();t(a.series,
|
||||
function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&0>=d&&(d=null);a.isXAxis?(d=c.xData,d.length&&(a.dataMin=T(n(a.dataMin,d[0]),La(d)),a.dataMax=x(n(a.dataMax,d[0]),va(d)))):(c.getExtremes(),e=c.dataMax,c=c.dataMin,w(c)&&w(e)&&(a.dataMin=T(n(a.dataMin,c),c),a.dataMax=x(n(a.dataMax,e),e)),w(d)&&(a.dataMin>=d?(a.dataMin=d,a.ignoreMinPadding=!0):a.dataMax<d&&(a.dataMax=d,a.ignoreMaxPadding=!0)))}})},translate:function(a,b,c,
|
||||
d,e,f){var g=1,h=0,k=d?this.oldTransA:this.transA;d=d?this.oldMin:this.min;var l=this.minPixelPadding;e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;k||(k=this.transA);c&&(g*=-1,h=this.len);this.reversed&&(g*=-1,h-=g*(this.sector||this.len));b?(a=a*g+h-l,a=a/k+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),"between"===f&&(f=.5),a=g*(a-d)*k+h+g*l+(ga(f)?k*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,
|
||||
b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,k,l,m=c&&f.oldChartHeight||f.chartHeight,q=c&&f.oldChartWidth||f.chartWidth,r;k=this.transB;e=n(e,this.translate(a,null,null,c));a=c=A(e+k);k=l=A(m-e-k);if(isNaN(e))r=!0;else if(this.horiz){if(k=h,l=m-this.bottom,a<g||a>g+this.width)r=!0}else if(a=g,c=q-this.right,k<h||k>h+this.height)r=!0;return r&&!d?null:f.renderer.crispLine(["M",a,k,"L",c,l],b||1)},getLinearTickPositions:function(a,
|
||||
b,c){var d,e=ha(K(b/a)*a),f=ha(ya(c/a)*a),g=[];if(b===c&&ga(b))return[b];for(b=e;b<=f;){g.push(b);b=ha(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog)for(e=b.length,a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,b[a-1],b[a],!0));else if(this.isDatetimeAxis&&"auto"===a.minorTickInterval)d=d.concat(this.getTimeTicks(this.normalizeTimeTickInterval(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&
|
||||
d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,k,l;this.isXAxis&&this.minRange===u&&!this.isLog&&(w(a.min)||w(a.max)?this.minRange=null:(t(this.series,function(a){k=a.xData;for(g=l=a.xIncrement?1:k.length-1;0<g;g--)if(h=k[g]-k[g-1],f===u||h<f)f=h}),this.minRange=T(5*f,this.dataMax-this.dataMin)));if(c-b<this.minRange){var m=this.minRange;d=
|
||||
(m-c+b)/2;d=[b-d,n(a.min,b-d)];e&&(d[2]=this.dataMin);b=va(d);c=[b+m,n(a.max,b+m)];e&&(c[2]=this.dataMax);c=La(c);c-b<m&&(d[0]=c-m,d[1]=n(a.min,c-m),b=va(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this,c=b.max-b.min,d=b.axisPointRange||0,e,f=0,g=0,h=b.linkedParent,k=!!b.categories,l=b.transA;if(b.isXAxis||k||d)h?(f=h.minPointOffset,g=h.pointRangePadding):t(b.series,function(a){var h=k?1:b.isXAxis?a.pointRange:b.axisPointRange||0,l=a.options.pointPlacement,p=a.closestPointRange;
|
||||
h>c&&(h=0);d=x(d,h);f=x(f,pa(l)?0:h/2);g=x(g,"on"===l?0:h);!a.noSharedTooltip&&w(p)&&(e=w(e)?T(e,p):p)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding=g*=h,b.pointRange=T(d,c),b.closestPointRange=e;a&&(b.oldTransA=l);b.translationSlope=b.transA=l=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=l*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=d.startOnTick,f=d.endOnTick,g=b.isLog,h=b.isDatetimeAxis,k=b.isXAxis,l=b.isLinked,m=
|
||||
b.options.tickPositioner,q=d.maxPadding,r=d.minPadding,p=d.tickInterval,Q=d.minTickInterval,D=d.tickPixelInterval,H,u=b.categories;l?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=n(c.min,c.dataMin),b.max=n(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&aa(11,1)):(b.min=n(b.userMin,d.min,b.dataMin),b.max=n(b.userMax,d.max,b.dataMax));g&&(!a&&0>=T(b.min,n(b.dataMin,b.min))&&aa(10,1),b.min=ha(ab(b.min)),b.max=ha(ab(b.max)));b.range&&w(b.max)&&(b.userMin=b.min=x(b.min,
|
||||
b.max-b.range),b.userMax=b.max,b.range=null);b.beforePadding&&b.beforePadding();b.adjustForMinRange();!(u||b.axisPointRange||b.usePercentage||l)&&w(b.min)&&w(b.max)&&(c=b.max-b.min)&&(w(d.min)||w(b.userMin)||!r||!(0>b.dataMin)&&b.ignoreMinPadding||(b.min-=c*r),w(d.max)||w(b.userMax)||!q||!(0<b.dataMax)&&b.ignoreMaxPadding||(b.max+=c*q));ga(d.floor)&&(b.min=x(b.min,d.floor));ga(d.ceiling)&&(b.max=T(b.max,d.ceiling));b.min===b.max||void 0===b.min||void 0===b.max?b.tickInterval=1:l&&!p&&D===b.linkedParent.options.tickPixelInterval?
|
||||
b.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=n(p,u?1:(b.max-b.min)*D/x(b.len,D)),!w(p)&&b.len<D&&!this.isRadial&&!this.isLog&&!u&&e&&f&&(H=!0,b.tickInterval/=4));k&&!a&&t(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();b.postProcessTickInterval&&(b.tickInterval=b.postProcessTickInterval(b.tickInterval));b.pointRange&&(b.tickInterval=x(b.pointRange,b.tickInterval));!p&&b.tickInterval<
|
||||
Q&&(b.tickInterval=Q);h||g||p||(b.tickInterval=ob(b.tickInterval,null,R.pow(10,K(R.log(b.tickInterval)/R.LN10)),n(d.allowDecimals,!(1<b.tickInterval&&5>b.tickInterval&&1E3<b.max&&9999>b.max))));b.minorTickInterval="auto"===d.minorTickInterval&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):m&&m.apply(b,[b.min,b.max]);a||(!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>x(2*b.len,200)&&aa(19,!0),a=h?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,
|
||||
d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):g?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),H&&a.splice(1,a.length-2),b.tickPositions=a);l||(d=a[0],g=a[a.length-1],h=b.minPointOffset||0,e?b.min=d:b.min-h>d&&a.shift(),f?b.max=g:b.max+h<g&&a.pop(),0===a.length&&w(d)&&a.push((g+d)/2),1===a.length&&(e=1E13<X(b.max)?1:.001,b.min-=e,b.max+=e))},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,
|
||||
d=this._maxTicksKey=[this.coll,this.pos,this.len].join("-");!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&!1!==this.options.alignTicks&&(b[d]=c.length);a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&!1!==this.options.alignTicks&&this.min!==u){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ha(b[b.length-1]+this.tickInterval));
|
||||
this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}w(d)&&a!==d&&(this.isDirty=!0)}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=this.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;t(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)for(c in a[b])a[b][c].total=null,a[b][c].cum=
|
||||
0;this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;this.isDirty||(this.isDirty=e||this.min!==this.oldMin||this.max!==this.oldMax)}else if(!this.isXAxis)for(b in this.oldStacks&&(a=this.stacks=this.oldStacks),a)for(c in a[b])a[b][c].cum=a[b][c].total;this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart;c=n(c,!0);e=v(e,{min:a,max:b});B(f,"setExtremes",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;
|
||||
f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){var c=this.dataMin,d=this.dataMax,e=this.options;this.allowZoomOutside||(w(c)&&a<=T(c,n(e.min,c))&&(a=u),w(d)&&b>=x(d,n(e.max,d))&&(b=u));this.displayBtn=a!==u||b!==u;this.setExtremes(a,b,!1,u,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=n(b.width,a.plotWidth-c+(b.offsetRight||0)),f=n(b.height,a.plotHeight),g=n(b.top,a.plotTop),b=n(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&
|
||||
(f=parseInt(f,10)/100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop);this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=x(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog;return{min:a?ha(ia(this.min)):this.min,max:a?ha(ia(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ia(this.min):this.min,
|
||||
b=b?ia(this.max):this.max;c>a||null===a?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},autoLabelAlign:function(a){a=(n(a,0)-90*this.side+720)%360;return 15<a&&165>a?"right":195<a&&345>a?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,k=b.inverted?[1,0,3,2][h]:h,l,m,q=0,r,p=0,Q=d.title,D=d.labels,H=0,V=b.axisOffset,b=b.clipOffset,W=[-1,1,1,-1][h],v,A=1,y=n(D.maxStaggerLines,5),z,B,G,C,oa;a.hasData=l=a.hasVisibleSeries||
|
||||
w(a.min)&&w(a.max)&&!!e;a.showAxis=m=l||n(d.showEmpty,!0);a.staggerLines=a.horiz&&D.staggerLines;a.axisGroup||(a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:D.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add());if(l||a.isLinked){a.labelAlign=n(D.align||a.autoLabelAlign(D.rotation));t(e,function(b){f[b]?f[b].addLabel():f[b]=new xa(a,b)});if(a.horiz&&!a.staggerLines&&
|
||||
y&&!D.rotation){for(l=a.reversed?[].concat(e).reverse():e;A<y;){z=[];B=!1;for(v=0;v<l.length;v++)G=l[v],C=(C=f[G].label&&f[G].label.getBBox())?C.width:0,oa=v%A,C&&(G=a.translate(G),z[oa]!==u&&G<z[oa]&&(B=!0),z[oa]=G+C);if(B)A++;else break}1<A&&(a.staggerLines=A)}t(e,function(b){if(0===h||2===h||{1:"left",3:"right"}[h]===a.labelAlign)H=x(f[b].getLabelSize(),H)});a.staggerLines&&(H*=a.staggerLines,a.labelOffset=H)}else for(v in f)f[v].destroy(),delete f[v];Q&&Q.text&&!1!==Q.enabled&&(a.axisTitle||(a.axisTitle=
|
||||
c.text(Q.text,0,0,Q.useHTML).attr({zIndex:7,rotation:Q.rotation||0,align:Q.textAlign||{low:"left",middle:"center",high:"right"}[Q.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(Q.style).add(a.axisGroup),a.axisTitle.isNew=!0),m&&(q=a.axisTitle.getBBox()[g?"height":"width"],r=Q.offset,p=w(r)?0:n(Q.margin,g?5:10)),a.axisTitle[m?"show":"hide"]());a.offset=W*n(d.offset,V[h]);c=2===h?a.tickBaseline:0;g=H+p+(H&&W*(g?n(D.y,a.tickBaseline+8):D.x)-c);a.axisTitleMargin=n(r,g);V[h]=x(V[h],
|
||||
a.axisTitleMargin+q+W*a.offset,g);b[k]=x(b[k],2*K(d.lineWidth/2))},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,
|
||||
k=E(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(2===this.side?k:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,k=a.tickPositions,l,m=a.axisTitle,q=a.ticks,r=a.minorTicks,p=a.alternateBands,n=f.stackLabels,D=f.alternateGridColor,H=a.tickmarkOffset,v=f.lineWidth,
|
||||
x=d.hasRendered&&w(a.oldMin)&&!isNaN(a.oldMin),A=a.hasData,y=a.showAxis,z,B=f.labels.overflow,G=a.justifyLabels=b&&!1!==B,E;a.labelEdge.length=0;a.justifyToPlot="justify"===B;t([q,r,p],function(a){for(var b in a)a[b].isActive=!1});if(A||h)a.minorTickInterval&&!a.categories&&t(a.getMinorTickPositions(),function(b){r[b]||(r[b]=new xa(a,b,"minor"));x&&r[b].isNew&&r[b].render(null,!0);r[b].render(null,!1,1)}),k.length&&(l=k.slice(),(b&&c||!b&&!c)&&l.reverse(),G&&(l=l.slice(1).concat([l[0]])),t(l,function(b,
|
||||
c){G&&(c=c===l.length-1?0:c+1);if(!h||b>=a.min&&b<=a.max)q[b]||(q[b]=new xa(a,b)),x&&q[b].isNew&&q[b].render(c,!0,.1),q[b].render(c)}),H&&0===a.min&&(q[-1]||(q[-1]=new xa(a,-1,null,!0)),q[-1].render(-1))),D&&t(k,function(b,c){0===c%2&&b<a.max&&(p[b]||(p[b]=new U.PlotLineOrBand(a)),z=b+H,E=k[c+1]!==u?k[c+1]+H:a.max,p[b].options={from:g?ia(z):z,to:g?ia(E):E,color:D},p[b].render(),p[b].isActive=!0)}),a._addedPlotLB||(t((f.plotLines||[]).concat(f.plotBands||[]),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=
|
||||
!0);t([q,r,p],function(a){var b,c,e=[],f=S?S.duration||500:0,g=function(){for(c=e.length;c--;)a[e[c]]&&!a[e[c]].isActive&&(a[e[c]].destroy(),delete a[e[c]])};for(b in a)a[b].isActive||(a[b].render(b,!1,0),a[b].isActive=!1,e.push(b));a!==p&&d.hasRendered&&f?f&&setTimeout(g,f):g()});v&&(b=a.getLinePath(v),a.axisLine?a.axisLine.animate({d:b}):a.axisLine=e.path(b).attr({stroke:f.lineColor,"stroke-width":v,zIndex:7}).add(a.axisGroup),a.axisLine[y?"show":"hide"]());m&&y&&(m[m.isNew?"attr":"animate"](a.getTitlePosition()),
|
||||
m.isNew=!1);n&&n.enabled&&a.renderStackTotals();a.isDirty=!1},redraw:function(){this.render();t(this.plotLinesAndBands,function(a){a.render()});t(this.series,function(a){a.isDirty=!0})},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||L(b);for(d in c)Ma(c[d]),c[d]=null;t([b.ticks,b.minorTicks,b.alternateBands],function(a){Ma(a)});for(a=e.length;a--;)e[a].destroy();t("stackTotalGroup axisLine axisTitle axisGroup cross gridGroup labelGroup".split(" "),function(a){b[a]&&(b[a]=b[a].destroy())});
|
||||
this.cross&&this.cross.destroy()},drawCrosshair:function(a,b){if(this.crosshair)if(!1===(w(b)||!n(this.crosshair.snap,!0)))this.hideCrosshair();else{var c,d=this.crosshair,e=d.animation;n(d.snap,!0)?w(b)&&(c=this.chart.inverted!=this.horiz?b.plotX:this.len-b.plotY):c=this.horiz?a.chartX-this.pos:this.len-a.chartY+this.pos;c=this.isRadial?this.getPlotLinePath(this.isXAxis?b.x:n(b.stackY,b.y)):this.getPlotLinePath(null,null,null,null,c);if(null===c)this.hideCrosshair();else if(this.cross)this.cross.attr({visibility:"visible"})[e?
|
||||
"animate":"attr"]({d:c},e);else e={"stroke-width":d.width||1,stroke:d.color||"#C0C0C0",zIndex:d.zIndex||2},d.dashStyle&&(e.dashstyle=d.dashStyle),this.cross=this.chart.renderer.path(c).attr(e).add()}},hideCrosshair:function(){this.cross&&this.cross.hide()}};v(ma.prototype,void 0);ma.prototype.getTimeTicks=function(a,b,c,d){var e=[],f={},g=M.global.useUTC,h,k=new la(b-wa),l=a.unitRange,m=a.count;if(w(b)){l>=z.second&&(k.setMilliseconds(0),k.setSeconds(l>=z.minute?0:m*K(k.getSeconds()/m)));if(l>=z.minute)k[qb](l>=
|
||||
z.hour?0:m*K(k[db]()/m));if(l>=z.hour)k[rb](l>=z.day?0:m*K(k[eb]()/m));if(l>=z.day)k[gb](l>=z.month?1:m*K(k[Da]()/m));l>=z.month&&(k[sb](l>=z.year?0:m*K(k[Qa]()/m)),h=k[Ra]());if(l>=z.year)k[tb](h-h%m);if(l===z.week)k[gb](k[Da]()-k[fb]()+n(d,1));b=1;wa&&(k=new la(k.getTime()+wa));h=k[Ra]();d=k.getTime();for(var q=k[Qa](),r=k[Da](),p=(z.day+(g?wa:6E4*k.getTimezoneOffset()))%z.day;d<c;)e.push(d),d=l===z.year?Pa(h+b*m,0):l===z.month?Pa(h,q+b*m):g||l!==z.day&&l!==z.week?d+l*m:Pa(h,q,r+b*m*(l===z.day?
|
||||
1:7)),b++;e.push(d);t(jb(e,function(a){return l<=z.hour&&a%z.day===p}),function(a){f[a]="day"})}e.info=v(a,{higherRanks:f,totalRange:l*m});return e};ma.prototype.normalizeTimeTickInterval=function(a,b){var c=b||[["millisecond",[1,2,5,10,20,25,50,100,200,500]],["second",[1,2,5,10,15,30]],["minute",[1,2,5,10,15,30]],["hour",[1,2,3,4,6,8,12]],["day",[1,2]],["week",[1,2]],["month",[1,2,3,4,6]],["year",null]],d=c[c.length-1],e=z[d[0]],f=d[1],g;for(g=0;g<c.length&&!(d=c[g],e=z[d[0]],f=d[1],c[g+1]&&a<=(e*
|
||||
f[f.length-1]+z[c[g+1][0]])/2);g++);e===z.year&&a<5*e&&(f=[1,2,5]);c=ob(a/e,f,"year"===d[0]?x(R.pow(10,K(R.log(a/e)/R.LN10)),1):1);return{unitRange:e,count:c,unitName:d[0]}};var zb=U.Tooltip=function(){this.init.apply(this,arguments)};zb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=E(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,
|
||||
fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});ca||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){this.label&&(this.label=this.label.destroy());clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=!1!==e.options.animation&&!e.isHidden&&(1<X(a-f.x)||1<X(b-f.y)),h=e.followPointer||1<e.len;v(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?u:g?(2*f.anchorX+c)/
|
||||
3:c,anchorY:h?u:g?(f.anchorY+d)/2:d});e.label.attr(f);g&&(clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32))},hide:function(a){var b=this,c;clearTimeout(this.hideTimer);this.isHidden||(c=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){b.label.fadeOut();b.isHidden=!0},n(a,this.options.hideDelay,500)),c&&t(c,function(a){a.setState()}),this.chart.hoverPoints=null)},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=
|
||||
0,k;a=ea(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===u&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(t(a,function(a){k=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&k?k.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&1<a.length&&b?b.chartY-f:e?d.plotHeight-g:h]);return kb(c,A)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],k=["x",d.chartWidth,
|
||||
a,c.plotX+d.plotLeft],l=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,m=function(a,b,c,d){var g=c<d-e;b=d+e+c<b;c=d-e-c;d+=e;if(l&&b)f[a]=d;else if(!l&&g)f[a]=c;else if(g)f[a]=c;else if(b)f[a]=d;else return!1},q=function(a,b,c,d){if(d<e||d>b-e)return!1;f[a]=d<c/2?1:d>b-c/2?b-c-2:d-c/2},r=function(a){var b=h;h=k;k=b;g=a},p=function(){!1!==m.apply(0,h)?!1!==q.apply(0,k)||g||(r(!0),p()):g?f.x=f.y=0:(r(!0),p())};(d.inverted||1<this.len)&&r();p();return f},defaultFormatter:function(a){var b=
|
||||
this.points||ea(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];t(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},k,l=[];k=e.formatter||this.defaultFormatter;var h=c.hoverPoints,m,q=this.shared;clearTimeout(this.hideTimer);this.followPointer=ea(a)[0].series.tooltipOptions.followPointer;
|
||||
g=this.getAnchor(a,b);f=g[0];g=g[1];!q||a.series&&a.series.noSharedTooltip?h=a.getLabelConfig():(c.hoverPoints=a,h&&t(h,function(a){a.setState()}),t(a,function(a){a.setState("hover");l.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=l,this.len=l.length,a=a[0]);k=k.call(h,this);h=a.series;this.distance=n(h.tooltipOptions.distance,16);!1===k?this.hide():(this.isHidden&&(Xa(d),d.attr("opacity",1).show()),d.attr({text:k}),m=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:m}),
|
||||
this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);B(c,"tooltipRefresh",{text:k,x:f+c.plotLeft,y:g+c.plotTop,borderColor:m})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(A(c.x),A(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b=a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&"datetime"===
|
||||
f.options.type&&ga(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in z){if(z[h]>=f||z[h]<=z.day&&0<a.key%z[h]){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+e+"}"));return ua(c,{point:a,series:b})}};var Ba;Ga=y.documentElement.ontouchstart!==u;var Ab=U.Pointer=function(a,b){this.init(a,b)};Ab.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ca?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);
|
||||
this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};U.Tooltip&&b.tooltip.enabled&&(a.tooltip=new zb(a,b.tooltip),this.followTouchMove=b.tooltip.followTouchMove);this.setDOMEvents()},normalize:function(a,b){var c,d;a=a||window.event;a=Hb(a);a.target||(a.target=a.srcElement);d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;b||(this.chartPosition=b=Gb(this.chart.container));
|
||||
d.pageX===u?(c=x(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return v(a,{chartX:A(c),chartY:A(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};t(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,
|
||||
h=b.hoverSeries,k,l,m=b.chartWidth,q=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];k=c.length;for(l=0;l<k;l++)c[l].visible&&!1!==c[l].options.enableMouseTracking&&!c[l].noSharedTooltip&&!0!==c[l].singularTooltips&&c[l].tooltipPoints.length&&(e=c[l].tooltipPoints[q])&&e.series&&(e._dist=X(q-e.clientX),m=T(m,e._dist),f.push(e));for(k=f.length;k--;)f[k]._dist>m&&f.splice(k,1);f.length&&f[0].clientX!==this.hoverX&&(d.refresh(f,a),this.hoverX=f[0].clientX)}c=h&&h.tooltipOptions.followPointer;
|
||||
if(h&&h.tracker&&!c){if((e=h.tooltipPoints[q])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));d&&!this._onDocumentMouseMove&&(this._onDocumentMouseMove=function(a){if(Y[Ba])Y[Ba].pointer.onDocumentMouseMove(a)},F(y,"mousemove",this._onDocumentMouseMove));t(b.axes,function(b){b.drawCrosshair(a,n(e,g))})},reset:function(a,b){var c=this.chart,d=c.hoverSeries,e=c.hoverPoint,f=c.tooltip,g=f&&f.shared?c.hoverPoints:e;(a=a&&f&&g)&&ea(g)[0].plotX===
|
||||
u&&(a=!1);if(a)f.refresh(g),e&&e.setState(e.state,!0);else{if(e)e.onMouseOut();if(d)d.onMouseOut();f&&f.hide(b);this._onDocumentMouseMove&&(L(y,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=null);t(c.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;t(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&
|
||||
e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,k=b.plotTop,l=b.plotWidth,m=b.plotHeight,q,r=this.mouseDownX,p=this.mouseDownY,n=c.panKey&&a[c.panKey+"Key"];d<h?d=h:d>h+l&&(d=h+l);e<k?e=k:e>k+m&&(e=k+m);this.hasDragged=
|
||||
Math.sqrt(Math.pow(r-d,2)+Math.pow(p-e,2));10<this.hasDragged&&(q=b.isInsidePlot(r-h,p-k),b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&q&&!n&&!this.selectionMarker&&(this.selectionMarker=b.renderer.rect(h,k,f?1:l,g?1:m,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add()),this.selectionMarker&&f&&(d-=r,this.selectionMarker.attr({width:X(d),x:(0<d?0:d)+r})),this.selectionMarker&&g&&(d=e-p,this.selectionMarker.attr({height:X(d),y:(0<d?0:d)+p})),q&&!this.selectionMarker&&
|
||||
c.panning&&b.pan(a,c.panning))},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.attr?e.attr("x"):e.x,g=e.attr?e.attr("y"):e.y,h=e.attr?e.attr("width"):e.width,k=e.attr?e.attr("height"):e.height,l;if(this.hasDragged||c)t(b.axes,function(b){if(b.zoomEnabled){var c=b.horiz,e="touchend"===a.type?b.minPixelPadding:0,p=b.toValue((c?f:g)+e),c=b.toValue((c?f+h:g+k)-e);isNaN(p)||isNaN(c)||(d[b.coll].push({axis:b,
|
||||
min:T(p,c),max:x(p,c)}),l=!0)}}),l&&B(b,"selection",d,function(a){b.zoom(v(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}b&&(J(b.container,{cursor:b._cursor}),b.cancelClick=10<this.hasDragged,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[])},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){Y[Ba]&&Y[Ba].pointer.drop(a)},onDocumentMouseMove:function(a){var b=
|
||||
this.chart,c=this.chartPosition,d=b.hoverSeries;a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){var a=Y[Ba];a&&(a.pointer.reset(),a.pointer.chartPosition=null)},onContainerMouseMove:function(a){var b=this.chart;Ba=b.index;a=this.normalize(a);a.returnValue=!1;"mousedown"===b.mouseIsDown&&this.drag(a);!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-
|
||||
b.plotLeft,a.chartY-b.plotTop)||b.openMenu||this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=N(a,"class")){if(-1!==c.indexOf(b))return!0;if(-1!==c.indexOf("highcharts-container"))return!1}a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget||a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,
|
||||
e=b.plotTop;a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(B(c.series,"click",v(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(v(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&B(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};F(b,"mouseleave",
|
||||
a.onContainerMouseLeave);1===Ha&&F(y,"mouseup",a.onDocumentMouseUp);Ga&&(b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},1===Ha&&F(y,"touchend",a.onDocumentTouchEnd))},destroy:function(){var a;L(this.chart.container,"mouseleave",this.onContainerMouseLeave);Ha||(L(y,"mouseup",this.onDocumentMouseUp),L(y,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};var Za=U.Legend=function(a,b){this.init(a,
|
||||
b)};Za.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=n(b.padding,8),f=b.itemMarginTop||0;this.options=b;b.enabled&&(c.itemStyle=d,c.itemHiddenStyle=G(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=n(b.symbolWidth,16),c.pages=[],c.render(),F(c.chart,"endResize",function(){c.positionCheckboxes()}))},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,
|
||||
f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,k={fill:h},l;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(l in k.stroke=h,g=a.convertAttribs(g),g)d=g[l],d!==u&&(k[l]=d);f.attr(k)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);f&&(f.x=e,f.y=
|
||||
d)},destroyItem:function(a){var b=a.checkbox;t(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Na(a.checkbox)},destroy:function(){var a=this.group,b=this.box;b&&(this.box=b.destroy());a&&(this.group=a.destroy())},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;b&&(c=b.translateY,t(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,J(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",
|
||||
top:g+"px",display:g>c-6&&g<c+d-6?"":"none"}))}))},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;b.text&&(this.title||(this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,"legend-title").attr({zIndex:1}).css(b.style).add(this.group)),a=this.title.getBBox(),c=a.height,this.offsetWidth=a.width,this.contentGroup.attr({translateY:c}));this.titleHeight=c},renderItem:function(a){var b=this.chart,c=b.renderer,d=this.options,e="horizontal"===d.layout,f=this.symbolWidth,
|
||||
g=d.symbolPadding,h=this.itemStyle,k=this.itemHiddenStyle,l=this.padding,m=e?n(d.itemDistance,20):0,q=!d.rtl,r=d.width,p=d.itemMarginBottom||0,t=this.itemMarginTop,D=this.initialItemX,u=a.legendItem,v=a.series&&a.series.drawLegendSymbol?a.series:a,w=v.options,w=this.createCheckboxForItem&&w&&w.showCheckbox,y=d.useHTML;u||(a.legendGroup=c.g("legend-item").attr({zIndex:1}).add(this.scrollGroup),a.legendItem=u=c.text(d.labelFormat?ua(d.labelFormat,a):d.labelFormatter.call(a),q?f+g:-g,this.baseline||
|
||||
0,y).css(G(a.visible?h:k)).attr({align:q?"left":"right",zIndex:2}).add(a.legendGroup),this.baseline||(this.baseline=c.fontMetrics(h.fontSize,u).f+3+t,u.attr("y",this.baseline)),v.drawLegendSymbol(this,a),this.setItemEvents&&this.setItemEvents(a,u,y,h,k),this.colorizeItem(a,a.visible),w&&this.createCheckboxForItem(a));c=u.getBBox();f=a.checkboxOffset=d.itemWidth||a.legendItemWidth||f+g+c.width+m+(w?20:0);this.itemHeight=g=A(a.legendItemHeight||c.height);e&&this.itemX-D+f>(r||b.chartWidth-2*l-D-d.x)&&
|
||||
(this.itemX=D,this.itemY+=t+this.lastLineHeight+p,this.lastLineHeight=0);this.maxItemWidth=x(this.maxItemWidth,f);this.lastItemY=t+this.itemY+p;this.lastLineHeight=x(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=t+g+p,this.lastLineHeight=g);this.offsetWidth=r||x((e?this.itemX-D-m:f)+l,this.offsetWidth)},getAllItems:function(){var a=[];t(this.chart.series,function(b){var c=b.options;n(c.showInLegend,w(c.linkedTo)?!1:u,!0)&&(a=a.concat(b.legendItems||("point"===
|
||||
c.legendType?b.data:b)))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,k=a.box,l=a.options,m=a.padding,q=l.borderWidth,r=l.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;d||(a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup));a.renderTitle();e=a.getAllItems();cb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||
|
||||
0)});l.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;t(e,function(b){a.renderItem(b)});g=l.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(q||r)g+=m,h+=m,k?0<g&&0<h&&(k[k.isNew?"attr":"animate"](k.crisp({width:g,height:h})),k.isNew=!1):(a.box=k=c.rect(0,0,g,h,l.borderRadius,q||0).attr({stroke:l.borderColor,"stroke-width":q||0,fill:r||"none"}).add(d).shadow(l.shadow),k.isNew=!0),k[f?"show":"hide"]();a.legendWidth=g;a.legendHeight=h;t(e,function(b){a.positionItem(b)});
|
||||
f&&d.align(v({width:g,height:h},l),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+("top"===e.verticalAlign?-f:f)-this.padding,g=e.maxHeight,h,k=this.clipRect,l=e.navigation,m=n(l.animation,!0),q=l.arrowSize||12,r=this.nav,p=this.pages,u,D=this.allItems;"horizontal"===e.layout&&(f/=2);g&&(f=T(f,g));p.length=0;a>f&&!e.useHTML?(this.clipHeight=h=x(f-20-this.titleHeight-this.padding,0),
|
||||
this.currentPage=n(this.currentPage,1),this.fullHeight=a,t(D,function(a,b){var c=a._legendItemPos[1],d=A(a.legendItem.getBBox().height),e=p.length;if(!e||c-p[e-1]>h&&(u||c)!==p[e-1])p.push(u||c),e++;b===D.length-1&&c+d-p[e-1]>h&&p.push(c);c!==u&&(u=c)}),k||(k=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(k)),k.attr({height:h}),r||(this.nav=r=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,q,q).on("click",function(){b.scroll(-1,m)}).add(r),this.pager=d.text("",
|
||||
15,10).css(l.style).add(r),this.down=d.symbol("triangle-down",0,0,q,q).on("click",function(){b.scroll(1,m)}).add(r)),b.scroll(0),a=f):r&&(k.attr({height:c.chartHeight}),r.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0);return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,k=this.pager,l=this.padding;e>d&&(e=d);0<e&&(b!==u&&(S=n(b,this.chart.animation)),this.nav.attr({translateX:l,
|
||||
translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:1===e?g:h}).css({cursor:1===e?"default":"pointer"}),k.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c))}};var Lb=U.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,
|
||||
a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup;a=a.baseline-A(.3*e.fontMetrics(a.options.itemStyle.fontSize,this.legendItem).b);var g;b.lineWidth&&(g={"stroke-width":b.lineWidth},b.dashStyle&&(g.dashstyle=b.dashStyle),this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f));c&&!1!==c.enabled&&(b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,
|
||||
d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0)}};(/Trident\/7\.0/.test(na)||Ta)&&bb(Za.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Ea.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=G(M,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];
|
||||
this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Y.length;Y.push(f);Ha++;!1!==d.reflow&&F(f,"load",function(){f.initReflow()});if(e)for(g in e)F(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ca?!1:n(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=P[a.type||b.type||b.defaultSeriesType])||aa(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a;a=c?a:b;return 0<=d&&d<=this.plotWidth&&
|
||||
0<=a&&a<=this.plotHeight},adjustTickAmounts:function(){!1!==this.options.chart.alignTicks&&t(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,k=this.hasCartesianSeries,l=this.isDirtyBox,m=c.length,q=m,r=this.renderer,p=r.isHidden(),u=[];S=n(a,this.animation);p&&this.cloneRenderTo();for(this.layOutTitles();q--;)if(a=c[q],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(q=
|
||||
m;q--;)a=c[q],a.options.stacking&&(a.isDirty=!0);t(c,function(a){a.isDirty&&"point"===a.options.legendType&&(f=!0)});f&&e.options.enabled&&(e.render(),this.isDirtyLegend=!1);g&&this.getStacks();k&&(this.isResizing||(this.maxTicks=null,t(b,function(a){a.setScale()})),this.adjustTickAmounts());this.getMargins();k&&(t(b,function(a){a.isDirty&&(l=!0)}),t(b,function(a){a.isDirtyExtremes&&(a.isDirtyExtremes=!1,u.push(function(){B(a,"afterSetExtremes",v(a.eventArgs,a.getExtremes()));delete a.eventArgs}));
|
||||
(l||g)&&a.redraw()}));l&&this.drawChartBox();t(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);r.draw();B(this,"redraw");p&&this.cloneRenderTo(!0);t(u,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===a)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++)for(e=c[d].points||[],b=0;b<e.length;b++)if(e[b].id===a)return e[b];return null},getAxes:function(){var a=
|
||||
this,b=this.options,c=b.xAxis=ea(b.xAxis||{}),b=b.yAxis=ea(b.yAxis||{});t(c,function(a,b){a.index=b;a.isX=!0});t(b,function(a,b){a.index=b});c=c.concat(b);t(c,function(b){new ma(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];t(this.series,function(b){a=a.concat(jb(b.points||[],function(a){return a.selected}))});return a},getSelectedSeries:function(){return jb(this.series,function(a){return a.selected})},getStacks:function(){var a=this;t(a.yAxis,function(a){a.stacks&&a.hasVisibleSeries&&
|
||||
(a.oldStacks=a.stacks)});t(a.series,function(b){!b.options.stacking||!0!==b.visible&&!1!==a.options.chart.ignoreHiddenSeries||(b.stackKey=b.type+n(b.options.stack,""))})},setTitle:function(a,b,c){var d=this,e=d.options,f;f=e.title=G(e.title,a);e=e.subtitle=G(e.subtitle,b);t([["title",a,f],["subtitle",b,e]],function(a){var b=a[0],c=d[b],e=a[1];a=a[2];c&&e&&(d[b]=c=c.destroy());a&&a.text&&!c&&(d[b]=d.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":"highcharts-"+b,zIndex:a.zIndex||4}).css(a.style).add())});
|
||||
d.layOutTitles(c)},layOutTitles:function(a){var b=0,c=this.title,d=this.subtitle,e=this.options,f=e.title,e=e.subtitle,g=this.renderer,h=this.spacingBox.width-44;c&&(c.css({width:(f.width||h)+"px"}).align(v({y:g.fontMetrics(f.style.fontSize,c).b-3},f),!1,"spacingBox"),f.floating||f.verticalAlign||(b=c.getBBox().height));d&&(d.css({width:(e.width||h)+"px"}).align(v({y:b+(f.margin-13)+g.fontMetrics(f.style.fontSize,d).b},e),!1,"spacingBox"),e.floating||e.verticalAlign||(b=ya(b+d.getBBox().height)));
|
||||
c=this.titleOffset!==b;this.titleOffset=b;!this.isDirtyBox&&c&&(this.isDirtyBox=c,this.hasRendered&&n(a,!0)&&this.isDirtyBox&&this.redraw())},getChartSize:function(){var a=this.options.chart,b=a.width,a=a.height,c=this.renderToClone||this.renderTo;w(b)||(this.containerWidth=Va(c,"width"));w(a)||(this.containerHeight=Va(c,"height"));this.chartWidth=x(0,b||this.containerWidth||600);this.chartHeight=x(0,n(a,19<this.containerHeight?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,
|
||||
c=this.container;a?b&&(this.renderTo.appendChild(c),Na(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),J(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+hb++;pa(a)&&(this.renderTo=a=y.getElementById(a));
|
||||
a||aa(13,!0);c=E(N(a,"data-highcharts-chart"));!isNaN(c)&&Y[c]&&Y[c].hasRendered&&Y[c].destroy();N(a,"data-highcharts-chart",this.index);a.innerHTML="";b.skipClone||a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=ja("div",{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},v({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},
|
||||
b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new Ja(a,c,d,b.style,!0):new Ja(a,c,d,b.style);ca&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=n(e.margin,20),g=e.x,h=e.y,k=e.align,l=e.verticalAlign,m=this.titleOffset;this.resetMargins();b=this.axisOffset;m&&!w(d[0])&&(this.plotTop=x(this.plotTop,m+this.options.title.margin+a[0]));c.display&&!e.floating&&("right"===k?w(d[1])||
|
||||
(this.marginRight=x(this.marginRight,c.legendWidth-g+f+a[1])):"left"===k?w(d[3])||(this.plotLeft=x(this.plotLeft,c.legendWidth+g+f+a[3])):"top"===l?w(d[0])||(this.plotTop=x(this.plotTop,c.legendHeight+h+f+a[0])):"bottom"!==l||w(d[2])||(this.marginBottom=x(this.marginBottom,c.legendHeight-h+f+a[2])));this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&t(this.axes,function(a){a.getOffset()});w(d[3])||(this.plotLeft+=
|
||||
b[3]);w(d[0])||(this.plotTop+=b[0]);w(d[2])||(this.marginBottom+=b[2]);w(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||Va(d,"width"),f=c.height||Va(d,"height"),c=a?a.target:I,d=function(){b.container&&(b.setSize(e,f,!1),b.hasUserSize=null)};if(!b.hasUserSize&&e&&f&&(c===I||c===y)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth=e;b.containerHeight=
|
||||
f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};F(I,"resize",b);F(a,"destroy",function(){L(I,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&B(d,"endResize",null,function(){--d.isResizing})};S=n(c,d.animation);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;w(a)&&(d.chartWidth=e=x(0,A(a)),d.hasUserSize=!!e);w(b)&&(d.chartHeight=f=x(0,A(b)));(S?Wa:J)(d.container,{width:e+"px",height:f+"px"},S);d.setChartSize(!0);d.renderer.setSize(e,
|
||||
f,c);d.maxTicks=null;t(d.axes,function(a){a.isDirty=!0;a.setScale()});t(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;B(d,"resize");!1===S?g():setTimeout(g,S&&S.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,k,l,m,q;this.plotLeft=k=A(this.plotLeft);this.plotTop=l=A(this.plotTop);this.plotWidth=
|
||||
m=x(0,A(d-k-this.marginRight));this.plotHeight=q=x(0,A(e-l-this.marginBottom));this.plotSizeX=b?q:m;this.plotSizeY=b?m:q;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:k,y:l,width:m,height:q};d=2*K(this.plotBorderWidth/2);b=ya(x(d,h[3])/2);c=ya(x(d,h[0])/2);this.clipBox={x:b,y:c,width:K(this.plotSizeX-x(d,h[1])/2-b),height:x(0,K(this.plotSizeY-x(d,h[2])/2-c))};a||t(this.axes,function(a){a.setAxisSize();
|
||||
a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=n(b[0],a[0]);this.marginRight=n(b[1],a[1]);this.marginBottom=n(b[2],a[2]);this.plotLeft=n(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,k=a.borderWidth||0,l=a.backgroundColor,m=a.plotBackgroundColor,q=a.plotBackgroundImage,
|
||||
r=a.plotBorderWidth||0,p,n=this.plotLeft,t=this.plotTop,u=this.plotWidth,v=this.plotHeight,w=this.plotBox,x=this.clipRect,A=this.clipBox;p=k+(a.shadow?8:0);if(k||l)e?e.animate(e.crisp({width:c-p,height:d-p})):(e={fill:l||"none"},k&&(e.stroke=a.borderColor,e["stroke-width"]=k),this.chartBackground=b.rect(p/2,p/2,c-p,d-p,a.borderRadius,k).attr(e).addClass("highcharts-background").add().shadow(a.shadow));m&&(f?f.animate(w):this.plotBackground=b.rect(n,t,u,v,0).attr({fill:m}).add().shadow(a.plotShadow));
|
||||
q&&(h?h.animate(w):this.plotBGImage=b.image(q,n,t,u,v).add());x?x.animate({width:A.width,height:A.height}):this.clipRect=b.clipRect(A);r&&(g?g.animate(g.crisp({x:n,y:t,width:u,height:v,strokeWidth:-r})):this.plotBorder=b.rect(n,t,u,v,0,-r).attr({stroke:a.plotBorderColor,"stroke-width":r,fill:"none",zIndex:1}).add());this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;t(["inverted","angular","polar"],function(g){c=P[b.type||b.defaultSeriesType];f=a[g]||
|
||||
b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=P[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;t(b,function(a){a.linkedSeries.length=0});t(b,function(b){var d=b.options.linkedTo;pa(d)&&(d=":previous"===d?a.series[b.index-1]:a.get(d))&&(d.linkedSeries.push(b),b.linkedParent=d)})},renderSeries:function(){t(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;
|
||||
b.items&&t(b.items,function(c){var d=v(b.style,c.style),e=E(d.left)+a.plotLeft,f=E(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options;this.setTitle();this.legend=new Za(this,c.legend);this.getStacks();t(a,function(a){a.setScale()});this.getMargins();this.maxTicks=null;t(a,function(a){a.setTickPositions(!0);a.setMaxTicks()});this.adjustTickAmounts();this.getMargins();this.drawChartBox();
|
||||
this.hasCartesianSeries&&t(a,function(a){a.render()});this.seriesGroup||(this.seriesGroup=b.g("series-group").attr({zIndex:3}).add());this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){a.enabled&&!this.credits&&(this.credits=this.renderer.text(a.text,0,0).on("click",function(){a.href&&(location.href=a.href)}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position))},destroy:function(){var a=this,b=a.axes,c=a.series,
|
||||
d=a.container,e,f=d&&d.parentNode;B(a,"destroy");Y[a.index]=u;Ha--;a.renderTo.removeAttribute("data-highcharts-chart");L(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();t("title subtitle chartBackground plotBackground plotBGImage plotBorder seriesGroup clipRect credits pointer scroller rangeSelector legend resetZoomButton tooltip renderer".split(" "),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});d&&(d.innerHTML="",L(d),f&&Na(d));for(e in a)delete a[e]},
|
||||
isReadyToRender:function(){var a=this;return!Z&&I==I.top&&"complete"!==y.readyState||ca&&!I.canvg?(ca?CanVGController.push(function(){a.firstRender()},a.options.global.canvasToolsURL):y.attachEvent("onreadystatechange",function(){y.detachEvent("onreadystatechange",a.firstRender);"complete"===y.readyState&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;a.isReadyToRender()&&(a.getContainer(),B(a,"init"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),
|
||||
t(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),B(a,"beforeRender"),U.Pointer&&(a.pointer=new Ab(a,b)),a.render(),a.renderer.draw(),c&&c.apply(a,[a]),t(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),B(a,"load"))},splashArray:function(a,b){var c=b[a],c=da(c)?c:[c,c,c,c];return[n(b[a+"Top"],c[0]),n(b[a+"Right"],c[1]),n(b[a+"Bottom"],c[2]),n(b[a+"Left"],c[3])]}};Ea.prototype.callbacks=[];var Ca=function(){};Ca.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,
|
||||
c);this.pointAttr={};a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length&&(a.colorCounter=0));a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.options.pointValKey||c.pointValKey;a=Ca.prototype.optionsToObject.call(this,a);v(this,a);this.options=this.options?v(this.options,a):a;d&&(this.y=this[d]);this.x===u&&c&&(this.x=b===u?c.autoIncrement():b);return this},optionsToObject:function(a){var b=
|
||||
{},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if("number"===typeof a||null===a)b[d[0]]=a;else if(ta(a))for(a.length>e&&(c=typeof a[0],"string"===c?b.name=a[0]:"number"===c&&(b.x=a[0]),f++);g<e;)b[d[g++]]=a[f++];else"object"===typeof a&&(b=a,a.dataLabels&&(c._hasPointLabels=!0),a.marker&&(c._hasPointMarkers=!0));return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;b&&(this.setState(),qa(b,this),b.length||(a.hoverPoints=null));if(this===a.hoverPoint)this.onMouseOut();
|
||||
if(this.graphic||this.dataLabel)L(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a="graphic dataLabel dataLabelUpper group connector shadowGroup".split(" "),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},tooltipFormatter:function(a){var b=
|
||||
this.series,c=b.tooltipOptions,d=n(c.valueDecimals,""),e=c.valuePrefix||"",f=c.valueSuffix||"";t(b.pointArrayMap||["y"],function(b){b="{point."+b;if(e||f)a=a.replace(b+"}",e+b+"}"+f);a=a.replace(b+"}",b+":,."+d+"f}")});return ua(a,{point:this,series:this.series})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();"click"===a&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||
|
||||
a.shiftKey)});B(this,a,b,c)}};var fa=function(){};fa.prototype={isCartesian:!0,type:"line",pointClass:Ca,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},axisTypes:["xAxis","yAxis"],colorCounter:0,parallelArrays:["x","y"],init:function(a,b){var c=this,d,e,f=a.series,g=function(a,b){return n(a.options.index,a._i)-n(b.options.index,b._i)};c.chart=a;c.options=b=c.setOptions(b);c.linkedSeries=[];c.bindAxes();v(c,{name:b.name,state:"",
|
||||
pointAttr:{},visible:!1!==b.visible,selected:!0===b.selected});ca&&(b.animation=!1);e=b.events;for(d in e)F(c,d,e[d]);if(e&&e.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;c.getColor();c.getSymbol();t(c.parallelArrays,function(a){c[a+"Data"]=[]});c.setData(b.data,!1);c.isCartesian&&(a.hasCartesianSeries=!0);f.push(c);c._i=f.length-1;cb(f,g);this.yAxis&&cb(this.yAxis.series,g);t(f,function(a,b){a.index=b;a.name=a.name||"Series "+(b+1)})},bindAxes:function(){var a=
|
||||
this,b=a.options,c=a.chart,d;t(a.axisTypes||[],function(e){t(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==u&&b[e]===d.id||b[e]===u&&0===d.index)c.series.push(a),a[e]=c,c.isDirty=!0});a[e]||a.optionalAxis===e||aa(18,!0)})},updateParallelArrays:function(a,b){var c=a.series,d=arguments;t(c.parallelArrays,"number"===typeof b?function(d){var f="y"===d&&c.toYData?c.toYData(a):a[d];c[d+"Data"][b]=f}:function(a){Array.prototype[b].apply(c[a+"Data"],Array.prototype.slice.call(d,2))})},autoIncrement:function(){var a=
|
||||
this.options,b=this.xIncrement,b=n(b,a.pointStart,0);this.pointInterval=n(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)null===d[c].y&&d.splice(c,1);d.length&&(b=[d])}else t(d,function(c,g){null===c.y?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,
|
||||
b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=G(e,c.series,a);this.tooltipOptions=G(M.tooltip,M.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);null===e.marker&&delete c.marker;return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(w(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint||this.getCyclic("color",
|
||||
this.options.color||Ya[this.type].color,this.chart.options.colors)},getSymbol:function(){var a=this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);/^url/.test(this.symbol)&&(a.radius=0)},drawLegendSymbol:Lb.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,k=e.options,l=e.chart,m=null,q=e.xAxis,r=q&&!!q.categories,p=e.tooltipPoints,v=k.turboThreshold,D=this.xData,w=this.yData,x=(h=e.pointArrayMap)&&h.length;a=a||[];h=a.length;b=n(b,!0);
|
||||
if(!1===d||!h||g!==h||e.cropped||e.hasGroupedData){e.xIncrement=null;e.pointRange=r?1:k.pointRange;e.colorCounter=0;t(this.parallelArrays,function(a){e[a+"Data"].length=0});if(v&&h>v){for(c=0;null===m&&c<h;)m=a[c],c++;if(ga(m)){r=n(k.pointStart,0);k=n(k.pointInterval,1);for(c=0;c<h;c++)D[c]=r,w[c]=a[c],r+=k;e.xIncrement=r}else if(ta(m))if(x)for(c=0;c<h;c++)k=a[c],D[c]=k[0],w[c]=k.slice(1,x+1);else for(c=0;c<h;c++)k=a[c],D[c]=k[0],w[c]=k[1];else aa(12)}else for(c=0;c<h;c++)a[c]!==u&&(k={series:e},
|
||||
e.pointClass.prototype.applyOptions.apply(k,[a[c]]),e.updateParallelArrays(k,c),r&&k.name&&(q.names[k.x]=k.name));pa(w[0])&&aa(14,!0);e.data=[];e.options.data=a;for(c=g;c--;)f[c]&&f[c].destroy&&f[c].destroy();p&&(p.length=0);q&&(q.minRange=q.userMinRange);e.isDirty=e.isDirtyData=l.isDirtyBox=!0;c=!1}else t(a,function(a,b){f[b].update(a,!1,null,!1)});b&&l.redraw(c)},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,k,l=this.options;k=l.cropThreshold;var m=
|
||||
0,q=this.isCartesian,r,p;if(q&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;h&&(r=h.getExtremes(),p=r.min,r=r.max);if(q&&this.sorted&&(!k||d>k||this.forceCrop))if(b[d-1]<p||b[0]>r)b=[],c=[];else if(b[0]<p||b[d-1]>r)e=this.cropData(this.xData,this.yData,p,r),b=e.xData,c=e.yData,e=e.start,f=!0,m=b.length;for(k=b.length-1;0<=k;k--)d=b[k]-b[k-1],!f&&b[k]>p&&b[k]<r&&m++,0<d&&(g===u||d<g)?g=d:0>d&&this.requireSorting&&aa(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=
|
||||
c;this.activePointCount=m;null===l.pointRange&&(this.pointRange=g||1);this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=n(this.cropShoulder,1),k;for(k=0;k<e;k++)if(a[k]>=c){f=x(0,k-h);break}for(;k<e;k++)if(a[k]>d){g=k+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,k,l=this.hasGroupedData,m,q=[],
|
||||
r;b||l||(b=[],b.length=a.length,b=this.data=b);for(r=0;r<g;r++)k=h+r,l?q[r]=(new f).init(this,[d[r]].concat(ea(e[r]))):(b[k]?m=b[k]:a[k]!==u&&(b[k]=m=(new f).init(this,a[k],d[r])),q[r]=m),q[r].index=k;if(b&&(g!==(c=b.length)||l))for(r=0;r<c;r++)r!==h||l||(r+=g),b[r]&&(b[r].destroyElements(),b[r].plotX=u);this.data=b;this.points=q},getExtremes:function(a){var b=this.yAxis,c=this.processedXData,d,e=[],f=0;d=this.xAxis.getExtremes();var g=d.min,h=d.max,k,l,m,q;a=a||this.stackedYData||this.processedYData;
|
||||
d=a.length;for(q=0;q<d;q++)if(l=c[q],m=a[q],k=null!==m&&m!==u&&(!b.isLog||m.length||0<m),l=this.getExtremesFromAll||this.cropped||(c[q+1]||l)>=g&&(c[q-1]||l)<=h,k&&l)if(k=m.length)for(;k--;)null!==m[k]&&(e[f++]=m[k]);else e[f++]=m;this.dataMin=n(void 0,La(e));this.dataMax=n(void 0,va(e))},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,k=a.pointPlacement,
|
||||
l="between"===k||ga(k),m=a.threshold,a=0;a<g;a++){var q=f[a],r=q.x,p=q.y,t=q.low,v=b&&e.stacks[(this.negStacks&&p<m?"-":"")+this.stackKey];e.isLog&&0>=p&&(q.y=p=null,aa(10));q.plotX=c.translate(r,0,0,0,1,k,"flags"===this.type);b&&this.visible&&v&&v[r]&&(v=v[r],p=v.points[this.index+","+a],t=p[0],p=p[1],0===t&&(t=n(m,e.min)),e.isLog&&0>=t&&(t=null),q.total=q.stackTotal=v.total,q.percentage=v.total&&q.y/v.total*100,q.stackY=p,v.setOffset(this.pointXOffset||0,this.barW||0));q.yBottom=w(t)?e.translate(t,
|
||||
0,1,0,1):null;h&&(p=this.modifyValue(p,q));q.plotY="number"===typeof p&&Infinity!==p?e.translate(p,0,1,0,1):u;q.clientX=l?c.translate(r,0,0,0,1):q.plotX;q.negative=q.y<(m||0);q.category=d&&d[q.x]!==u?d[q.x]:q.x}this.getSegments()},animate:function(a){var b=this.chart,c=b.renderer,d;d=this.options.animation;var e=this.clipBox||b.clipBox,f=b.inverted,g;d&&!da(d)&&(d=Ya[this.type].animation);g=["_sharedClip",d.duration,d.easing,e.height].join();a?(a=b[g],d=b[g+"m"],a||(b[g]=a=c.clipRect(v(e,{width:0})),
|
||||
b[g+"m"]=d=c.clipRect(-99,f?-b.plotLeft:-b.plotTop,99,f?b.chartWidth:b.chartHeight)),this.group.clip(a),this.markerGroup.clip(d),this.sharedClipKey=g):((a=b[g])&&a.animate({width:b.plotSizeX},d),b[g+"m"]&&b[g+"m"].animate({width:b.plotSizeX+99},d),this.animate=null)},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group,d=this.clipBox;c&&!1!==this.options.clip&&(b&&d||c.clip(d?a.renderer.clipRect(d):a.clipRect),this.markerGroup.clip());B(this,"afterAnimate");setTimeout(function(){b&&
|
||||
a[b]&&(d||(a[b]=a[b].destroy()),a[b+"m"]&&(a[b+"m"]=a[b+"m"].destroy()))},100)},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,k,l,m,q=this.options.marker,r=this.pointAttr[""],p,t,w,x=this.markerGroup,y=n(q.enabled,!this.requireSorting||this.activePointCount<.5*this.xAxis.len/q.radius);if(!1!==q.enabled||this._hasPointMarkers)for(f=b.length;f--;)g=b[f],d=K(g.plotX),e=g.plotY,m=g.graphic,p=g.marker||{},t=!!g.marker,a=y&&p.enabled===u||p.enabled,w=c.isInsidePlot(A(d),e,c.inverted),
|
||||
a&&e!==u&&!isNaN(e)&&null!==g.y?(a=g.pointAttr[g.selected?"select":""]||r,h=a.r,k=n(p.symbol,this.symbol),l=0===k.indexOf("url"),m?m[w?"show":"hide"](!0).animate(v({x:d-h,y:e-h},m.symbolName?{width:2*h,height:2*h}:{})):w&&(0<h||l)&&(g.graphic=c.renderer.symbol(k,d-h,e-h,2*h,2*h,t?p:q).attr(a).add(x))):m&&(g.graphic=m.destroy())},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={};a=a||{};b=b||{};c=c||{};d=d||{};for(f in e)g=e[f],h[f]=n(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=
|
||||
this,b=a.options,c=Ya[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],k,l=[],m,q=a.pointAttrToOptions;m=a.hasPointSpecificOptions;var r=b.negativeColor,p=c.lineColor,n=c.fillColor;k=b.turboThreshold;var u;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):e.color=e.color||za(e.color||g).brighten(e.brightness).get();l[""]=a.convertAttribs(c,f);t(["hover","select"],function(b){l[b]=a.convertAttribs(d[b],
|
||||
l[""])});a.pointAttr=l;g=h.length;if(!k||g<k||m)for(;g--;){k=h[g];(c=k.options&&k.options.marker||k.options)&&!1===c.enabled&&(c.radius=0);k.negative&&r&&(k.color=k.fillColor=r);m=b.colorByPoint||k.color;if(k.options)for(u in q)w(c[q[u]])&&(m=!0);m?(c=c||{},m=[],d=c.states||{},f=d.hover=d.hover||{},b.marker||(f.color=f.color||!k.options.color&&e.color||za(k.color).brighten(f.brightness||e.brightness).get()),f={color:k.color},n||(f.fillColor=k.color),p||(f.lineColor=k.color),m[""]=a.convertAttribs(v(f,
|
||||
c),l[""]),m.hover=a.convertAttribs(d.hover,l.hover,m[""]),m.select=a.convertAttribs(d.select,l.select,m[""])):m=l;k.pointAttr=m}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(na),d,e,f=a.data||[],g,h,k;B(a,"destroy");L(a);t(a.axisTypes||[],function(b){if(k=a[b])qa(k.series,a),k.isDirty=k.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);t("area graph dataLabelsGroup group markerGroup tracker graphNeg areaNeg posClip negClip".split(" "),
|
||||
function(b){a[b]&&(d=c&&"group"===b?"hide":"destroy",a[b][d]())});b.hoverSeries===a&&(b.hoverSeries=null);qa(b.series,a);for(h in a)delete a[h]},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;t(a,function(e,f){var g=e.plotX,h=e.plotY,k;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(k=a[f-1],"right"===d?c.push(k.plotX,h):"center"===d?c.push((k.plotX+g)/2,k.plotY,(k.plotX+g)/2,h):c.push(g,k.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=
|
||||
this,b=[],c,d=[];t(a.segments,function(e){c=a.getSegmentPath(e);1<e.length?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f="square"!==b.linecap,g=this.getGraphPath(),h=b.negativeColor;h&&c.push(["graphNeg",h]);t(c,function(c,h){var m=c[0],q=a[m];q?(Xa(q),q.animate({d:g})):d&&g.length&&(q={stroke:c[1],"stroke-width":d,fill:"none",zIndex:1},e?q.dashstyle=e:f&&(q["stroke-linecap"]=
|
||||
q["stroke-linejoin"]="round"),a[m]=a.chart.renderer.path(g).attr(q).add(a.group).shadow(!h&&b.shadow))})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,k=this.negClip;e=b.chartWidth;var l=b.chartHeight,m=x(e,l),q=this.yAxis;d&&(f||g)&&(d=A(q.toPixels(a.threshold||0,!0)),0>d&&(m-=d),a={x:0,y:0,width:m,height:d},m={x:0,y:d,width:m,height:m},b.inverted&&(a.height=m.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-
|
||||
d-b.plotLeft,y:0,width:e,height:l},m={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e})),q.reversed?(b=m,e=a):(b=a,e=m),h?(h.animate(b),k.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=k=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(k)),g&&(g.clip(h),this.areaNeg.clip(k))))},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};t(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;b.xAxis&&(F(c,"resize",a),F(b,
|
||||
"destroy",function(){L(c,"resize",a)}),a(),b.invertGroups=a)},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis;a.inverted&&(b=c,c=this.xAxis);return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&
|
||||
b.renderer.isSVG&&n(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex,h=a.hasRendered,k=b.seriesGroup;c=a.plotGroup("group","series",f,g,k);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,k);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(),a.clipNeg());t(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&!1!==a.options.enableMouseTracking&&a.drawTracker();b.inverted&&
|
||||
a.invertGroups();!1===d.clip||a.sharedClipKey||h||c.clip(b.clipRect);e&&a.animate();h||(e?a.animationTimeout=setTimeout(function(){a.afterAnimate()},e):a.afterAnimate());a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:n(d&&d.left,a.plotLeft),translateY:n(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);
|
||||
this.render();b&&B(this,"updatedData")}};v(Ea.prototype,{addSeries:function(a,b,c){var d,e=this;a&&(b=n(b,!0),B(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options;new ma(this,G(a,{index:this[e].length,isX:b}));f[e]=ea(f[e]||{});f[e].push(a);n(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&J(d,{left:b.plotLeft+
|
||||
"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};d||(b.loadingDiv=d=ja("div",{className:"highcharts-loading"},v(e.style,{zIndex:10,display:"none"}),b.container),b.loadingSpan=ja("span",null,e.labelStyle,d),F(b,"redraw",f));b.loadingSpan.innerHTML=a||c.lang.loading;b.loadingShown||(J(d,{opacity:0,display:""}),Wa(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0);f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Wa(b,{opacity:0},
|
||||
{duration:a.loading.hideDuration||100,complete:function(){J(b,{display:"none"})}});this.loadingShown=!1}});v(Ca.prototype,{update:function(a,b,c,d){function e(){f.applyOptions(a);da(a)&&!ta(a)&&(f.redraw=function(){h&&(a&&a.marker&&a.marker.symbol?f.graphic=h.destroy():h.attr(f.pointAttr[f.state||""]));a&&a.dataLabels&&f.dataLabel&&(f.dataLabel=f.dataLabel.destroy());f.redraw=null});k=f.index;g.updateParallelArrays(f,k);m.data[k]=f.options;g.isDirty=g.isDirtyData=!0;!g.fixedBox&&g.hasCartesianSeries&&
|
||||
(l.isDirtyBox=!0);"point"===m.legendType&&l.legend.destroyItem(f);b&&l.redraw(c)}var f=this,g=f.series,h=f.graphic,k,l=g.chart,m=g.options;b=n(b,!0);!1===d?e():f.firePointEvent("update",{options:a},e)},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;S=n(b,f.animation);a=n(a,!0);c.firePointEvent("remove",null,function(){g=Ia(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.updateParallelArrays(c,"splice",g,1);c.destroy();d.isDirty=!0;d.isDirtyData=
|
||||
!0;a&&f.redraw()})}});v(fa.prototype,{addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,k=this.chart,l=this.xAxis&&this.xAxis.names,m=g&&g.shift||0,q=e.data,r,p=this.xData;S=n(d,k.animation);c&&t([g,h,this.graphNeg,this.areaNeg],function(a){a&&(a.shift=m+1)});h&&(h.isArea=!0);b=n(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=p.length;if(this.requireSorting&&g<p[h-1])for(r=!0;h&&p[h-1]>g;)h--;this.updateParallelArrays(d,"splice",
|
||||
h,0,0);this.updateParallelArrays(d,h);l&&d.name&&(l[g]=d.name);q.splice(h,0,a);r&&(this.data.splice(h,0,null),this.processData());"point"===e.legendType&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),this.updateParallelArrays(d,"shift"),q.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),k.redraw())},remove:function(a,b){var c=this,d=c.chart;a=n(a,!0);c.isRemoving||(c.isRemoving=!0,B(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();
|
||||
a&&d.redraw(b)}));c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=P[f].prototype,h=["group","markerGroup","dataLabelsGroup"],k;t(h,function(a){h[a]=c[a];delete c[a]});a=G(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(k in g)g.hasOwnProperty(k)&&(this[k]=u);v(this,P[a.type||f].prototype);t(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries();n(b,!0)&&d.redraw(!1)}});v(ma.prototype,{update:function(a,
|
||||
b){var c=this.chart;a=c.options[this.coll][this.options.index]=G(this.userOptions,a);this.destroy(!0);this._addedPlotLB=u;this.init(c,v(a,{events:u}));c.isDirtyBox=!0;n(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);qa(b.axes,this);qa(b[c],this);b.options[c].splice(this.options.index,1);t(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;n(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},
|
||||
b)},setCategories:function(a,b){this.update({categories:a},b)}});var Mb=nb(fa);P.line=Mb;fa.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,k,l;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),l=a.plotGroup("dataLabelsGroup","data-labels",d.defer?"hidden":"visible",d.zIndex||6),n(d.defer,!0)&&(l.attr({opacity:+h}),h||F(a,"afterAnimate",function(){a.visible&&l.show();l[b.animation?"animate":"attr"]({opacity:1},
|
||||
{duration:200})})),g=d,t(e,function(b){var e,h=b.dataLabel,p,t,x=b.connector,A=!0;f=b.options&&b.options.dataLabels;e=n(f&&f.enabled,g.enabled);if(h&&!e)b.dataLabel=h.destroy();else if(e){d=G(g,f);e=d.rotation;p=b.getLabelConfig();k=d.format?ua(d.format,p):d.formatter.call(p,d);d.style.color=n(d.color,d.style.color,a.color,"black");if(h)w(k)?(h.attr({text:k}),A=!1):(b.dataLabel=h=h.destroy(),x&&(b.connector=x.destroy()));else if(w(k)){h={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,
|
||||
r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(t in h)h[t]===u&&delete h[t];h=b.dataLabel=a.chart.renderer[e?"text":"label"](k,0,-999,null,null,null,d.useHTML).attr(h).css(v(d.style,c&&{cursor:c})).add(l).shadow(d.shadow)}h&&a.alignDataLabel(b,h,d,null,A)}})};fa.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=n(a.plotX,-999),k=n(a.plotY,-999),l=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,A(k),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-
|
||||
1,g)))d=v({x:g?f.plotWidth-k:h,y:A(g?f.plotHeight-h:k),width:0,height:0},d),v(c,{width:l.width,height:l.height}),c.rotation?b[e?"attr":"animate"]({x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2}).attr({align:c.align}):(b.align(c,null,d),g=b.alignAttr,"justify"===n(c.overflow,"justify")?this.justifyDataLabel(b,c,g,l,d,e):n(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+l.width,g.y+l.height)));a||(b.attr({y:-999}),b.placed=!1)};fa.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,
|
||||
h=b.align,k=b.verticalAlign,l,m;l=c.x;0>l&&("right"===h?b.align="left":b.x=-l,m=!0);l=c.x+d.width;l>g.plotWidth&&("left"===h?b.align="right":b.x=g.plotWidth-l,m=!0);l=c.y;0>l&&("bottom"===k?b.verticalAlign="top":b.y=-l,m=!0);l=c.y+d.height;l>g.plotHeight&&("top"===k?b.verticalAlign="bottom":b.y=g.plotHeight-l,m=!0);m&&(a.placed=!f,a.align(b,null,e))};P.pie&&(P.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=n(e.connectorPadding,10),g=n(e.connectorWidth,
|
||||
1),h=d.plotWidth,k=d.plotHeight,l,m,q=n(e.softConnector,!0),r=e.distance,p=a.center,u=p[2]/2,v=p[1],w=0<r,y,z,B,G=[[],[]],E,F,N,M,C,I=[0,0,0,0],R=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){fa.prototype.drawDataLabels.apply(a);t(b,function(a){a.dataLabel&&a.visible&&G[a.half].push(a)});for(M=2;M--;){var K=[],P=[],J=G[M],O=J.length,L;if(O){a.sortByAngle(J,M-.5);for(C=b=0;!b&&J[C];)b=J[C]&&J[C].dataLabel&&(J[C].dataLabel.getBBox().height||21),C++;if(0<r){z=T(v+u+r,d.plotHeight);
|
||||
for(C=x(0,v-u-r);C<=z;C+=b)K.push(C);z=K.length;if(O>z){c=[].concat(J);c.sort(R);for(C=O;C--;)c[C].rank=C;for(C=O;C--;)J[C].rank>=z&&J.splice(C,1);O=J.length}for(C=0;C<O;C++){c=J[C];B=c.labelPos;c=9999;var U,S;for(S=0;S<z;S++)U=X(K[S]-B[1]),U<c&&(c=U,L=S);if(L<C&&null!==K[C])L=C;else for(z<O-C+L&&null!==K[C]&&(L=z-O+C);null===K[L];)L++;P.push({i:L,y:K[L]});K[L]=null}P.sort(R)}for(C=0;C<O;C++){c=J[C];B=c.labelPos;y=c.dataLabel;N=!1===c.visible?"hidden":"visible";c=B[1];if(0<r){if(z=P.pop(),L=z.i,F=
|
||||
z.y,c>F&&null!==K[L+1]||c<F&&null!==K[L-1])F=T(x(0,c),d.plotHeight)}else F=c;E=e.justify?p[0]+(M?-1:1)*(u+r):a.getX(F===v-u-r||F===v+u+r?c:F,M);y._attr={visibility:N,align:B[6]};y._pos={x:E+e.x+({left:f,right:-f}[B[6]]||0),y:F+e.y-10};y.connX=E;y.connY=F;null===this.options.size&&(z=y.width,E-z<f?I[3]=x(A(z-E+f),I[3]):E+z>h-f&&(I[1]=x(A(E+z-h+f),I[1])),0>F-b/2?I[0]=x(A(-F+b/2),I[0]):F+b/2>k&&(I[2]=x(A(F+b/2-k),I[2])))}}}if(0===va(I)||this.verifyDataLabelOverflow(I))this.placeDataLabels(),w&&g&&t(this.points,
|
||||
function(b){l=b.connector;B=b.labelPos;(y=b.dataLabel)&&y._pos?(N=y._attr.visibility,E=y.connX,F=y.connY,m=q?["M",E+("left"===B[6]?5:-5),F,"C",E,F,2*B[2]-B[4],2*B[3]-B[5],B[2],B[3],"L",B[4],B[5]]:["M",E+("left"===B[6]?5:-5),F,"L",B[2],B[3],"L",B[4],B[5]],l?(l.animate({d:m}),l.attr("visibility",N)):b.connector=l=a.chart.renderer.path(m).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:N}).add(a.dataLabelsGroup)):l&&(b.connector=l.destroy())})}},P.pie.prototype.placeDataLabels=
|
||||
function(){t(this.points,function(a){a=a.dataLabel;var b;a&&((b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999}))})},P.pie.prototype.alignDataLabel=Eb,P.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;null!==d[0]?e=x(b[2]-x(a[1],a[3]),c):(e=x(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);null!==d[1]?e=x(T(e,b[2]-x(a[0],a[2])),c):(e=x(T(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),
|
||||
t(this.points,function(a){a.dataLabel&&(a.dataLabel._pos=null)}),this.drawDataLabels&&this.drawDataLabels()):f=!0;return f});P.column&&(P.column.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,k=a.below||a.plotY>n(this.translatedThreshold,f.plotSizeY),l=n(c.inside,!!this.options.stacking);h&&(d=G(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),l||(g?(d.x+=k?0:d.width,d.width=0):(d.y+=k?d.height:0,d.height=
|
||||
0)));c.align=n(c.align,!g||l?"center":k?"right":"left");c.verticalAlign=n(c.verticalAlign,g||l?"middle":k?"top":"bottom");fa.prototype.alignDataLabel.call(this,a,b,c,d,e)});var $a=U.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==u&&e!==b.hoverPoint)e.onMouseOver(c)};t(a.points,function(a){a.graphic&&(a.graphic.element.point=
|
||||
a);a.dataLabel&&(a.dataLabel.element.point=a)});a._hasTracking||(t(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),Ga))a[b].on("touchstart",f)}),a._hasTracking=!0)},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,k=f.options.tooltip.snap,l=a.tracker,m=b.cursor,q=m&&{cursor:m},m=a.singlePoints,n,
|
||||
p=function(){if(f.hoverSeries!==a)a.onMouseOver()},u="rgba(192,192,192,"+(Z?1E-4:.002)+")";if(e&&!c)for(n=e+1;n--;)"M"===d[n]&&d.splice(n+1,0,d[n+1]-k,d[n+2],"L"),(n&&"M"===d[n]||n===e)&&d.splice(n,0,"L",d[n-2]+k,d[n-1]);for(n=0;n<m.length;n++)e=m[n],d.push("M",e.plotX-k,e.plotY,"L",e.plotX+k,e.plotY);l?l.attr({d:d}):(a.tracker=h.path(d).attr({"stroke-linejoin":"round",visibility:a.visible?"visible":"hidden",stroke:u,fill:c?u:"none","stroke-width":b.lineWidth+(c?0:2*k),zIndex:2}).add(a.group),t([a.tracker,
|
||||
a.markerGroup],function(a){a.addClass("highcharts-tracker").on("mouseover",p).on("mouseout",function(a){g.onTrackerMouseOut(a)}).css(q);if(Ga)a.on("touchstart",p)}))}};P.column&&(ColumnSeries.prototype.drawTracker=$a.drawTrackerPoint);P.pie&&(P.pie.prototype.drawTracker=$a.drawTrackerPoint);P.scatter&&(ScatterSeries.prototype.drawTracker=$a.drawTrackerPoint);v(Za.prototype,{setItemEvents:function(a,b,c,d,e){var f=this;(c?b:a.legendGroup).on("mouseover",function(){a.setState("hover");b.css(f.options.itemHoverStyle)}).on("mouseout",
|
||||
function(){b.css(a.visible?d:e);a.setState()}).on("click",function(b){var c=function(){a.setVisible()};b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):B(a,"legendItemClick",b,c)})},createCheckboxForItem:function(a){a.checkbox=ja("input",{type:"checkbox",checked:a.selected,defaultChecked:a.selected},this.options.itemCheckboxStyle,this.chart.container);F(a.checkbox,"click",function(b){B(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})})}});M.legend.itemStyle.cursor=
|
||||
"pointer";v(Ea.prototype,{showResetZoom:function(){var a=this,b=M.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f="chart"===c.relativeTo?null:"plotBox";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;B(a,"selection",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?
|
||||
t(this.axes,function(a){b=a.zoom()}):t(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?"zoomX":"zoomY"]||c[h?"pinchX":"pinchY"])b=e.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;d&&!e?this.showResetZoom():!d&&da(e)&&(this.resetZoomButton=e.destroy());b&&this.redraw(n(this.options.chart.animation,a&&a.animation,100>this.pointCount))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&t(d,function(a){a.setState()});t("xy"===b?[1,0]:[1],function(b){var d=a[b?"chartX":
|
||||
"chartY"],h=c[b?"xAxis":"yAxis"][0],k=c[b?"mouseDownX":"mouseDownY"],l=(h.pointRange||0)/2,m=h.getExtremes(),n=h.toValue(k-d,!0)+l,k=h.toValue(k+c[b?"plotWidth":"plotHeight"]-d,!0)-l;h.series.length&&n>T(m.dataMin,m.min)&&k<x(m.dataMax,m.max)&&(h.setExtremes(n,k,!1,!1,{trigger:"pan"}),e=!0);c[b?"mouseDownX":"mouseDownY"]=d});e&&c.redraw(!1);J(c.container,{cursor:"move"})}});v(Ca.prototype,{select:function(a,b){var c=this,d=c.series,e=d.chart;a=n(a,!c.selected);c.firePointEvent(a?"select":"unselect",
|
||||
{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[Ia(c,d.data)]=c.options;c.setState(a&&"select");b||t(e.getSelectedPoints(),function(a){a.selected&&a!==c&&(a.selected=a.options.selected=!1,d.options.data[Ia(a,d.data)]=a.options,a.setState(""),a.firePointEvent("unselect"))})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent("mouseOver");!d||d.shared&&!b.noSharedTooltip||d.refresh(this,a);this.setState("hover");
|
||||
c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;this.firePointEvent("mouseOut");b&&-1!==Ia(this,b)||(this.setState(),a.hoverPoint=null)},importEvents:function(){if(!this.hasImportedEvents){var a=G(this.series.options.point,this.options).events,b;this.events=a;for(b in a)F(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a,b){var c=this.plotX,d=this.plotY,e=this.series,f=e.options.states,g=Ya[e.type].marker&&e.options.marker,h=g&&!g.enabled,k=g&&g.states[a],
|
||||
l=k&&!1===k.enabled,m=e.stateMarkerGraphic,n=this.marker||{},r=e.chart,p=e.halo,t;a=a||"";t=this.pointAttr[a]||e.pointAttr[a];if(!(a===this.state&&!b||this.selected&&"select"!==a||f[a]&&!1===f[a].enabled||a&&(l||h&&!1===k.enabled)||a&&n.states&&n.states[a]&&!1===n.states[a].enabled)){if(this.graphic)g=g&&this.graphic.symbolName&&t.r,this.graphic.attr(G(t,g?{x:c-g,y:d-g,width:2*g,height:2*g}:{})),m&&m.hide();else{if(a&&k)if(g=k.radius,n=n.symbol||e.symbol,m&&m.currentSymbol!==n&&(m=m.destroy()),m)m[b?
|
||||
"animate":"attr"]({x:c-g,y:d-g});else n&&(e.stateMarkerGraphic=m=r.renderer.symbol(n,c-g,d-g,2*g,2*g).attr(t).add(e.markerGroup),m.currentSymbol=n);if(m)m[a&&r.isInsidePlot(c,d,r.inverted)?"show":"hide"]()}(c=f[a]&&f[a].halo)&&c.size?(p||(e.halo=p=r.renderer.path().add(e.seriesGroup)),p.attr(v({fill:za(this.color||e.color).setOpacity(c.opacity).get()},c.attributes))[b?"animate":"attr"]({d:this.haloPath(c.size)})):p&&p.attr({d:[]});this.state=a}},haloPath:function(a){var b=this.series,c=b.chart,d=
|
||||
b.getPlotBox(),e=c.inverted;return c.renderer.symbols.circle(d.translateX+(e?b.yAxis.len-this.plotY:this.plotX)-a,d.translateY+(e?b.xAxis.len-this.plotX:this.plotY)-a,2*a,2*a)}});v(fa.prototype,{onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&B(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&
|
||||
B(this,"mouseOut");!c||a.stickyTracking||c.shared&&!this.noSharedTooltip||c.hide();this.setState();b.hoverSeries=null},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth;a=a||"";this.state!==a&&(this.state=a,e[a]&&!1===e[a].enabled||(a&&(b=e[a].lineWidth||b+(e[a].lineWidthPlus||0)),c&&!c.dashstyle&&(a={"stroke-width":b},c.attr(a),d&&d.attr(a))))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;
|
||||
f=(c.visible=a=c.userOptions.visible=a===u?!h:a)?"show":"hide";t(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&t(d.series,function(a){a.options.stacking&&a.visible&&(a.isDirty=!0)});t(c.linkedSeries,function(b){b.setVisible(a,!1)});g&&(d.isDirtyBox=!0);!1!==b&&d.redraw();B(c,f)},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||
|
||||
e.len:this.chart.plotSizeX,h,k,l=[];if(!1!==this.options.enableMouseTracking&&!this.singularTooltips){a&&(this.tooltipPoints=null);t(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(k=0;k<a;k++)if(e=b[k],c=e.x,c>=f.min&&c<=f.max)for(h=b[k+1],c=d===u?0:d+1,d=b[k+1]?T(x(0,K((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;0<=c&&c<=d;)l[c++]=e;this.tooltipPoints=l}},show:function(){this.setVisible(!0)},
|
||||
hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===u?!this.selected:a;this.checkbox&&(this.checkbox.checked=a);B(this,a?"select":"unselect")},drawTracker:$a.drawTrackerGraph});v(U,{Axis:ma,Chart:Ea,Color:za,Point:Ca,Tick:xa,Renderer:Ja,Series:fa,SVGElement:O,SVGRenderer:Ja,arrayMin:La,arrayMax:va,charts:Y,dateFormat:Ka,format:ua,pathAnim:ib,getOptions:function(){return M},hasBidiBug:Db,isTouchDevice:wb,numberFormat:ka,seriesTypes:P,setOptions:function(a){M=G(!0,M,a);pb();
|
||||
return M},addEvent:F,removeEvent:L,createElement:ja,discardElement:Na,css:J,each:t,extend:v,map:kb,merge:G,pick:n,splat:ea,extendClass:nb,pInt:E,wrap:bb,svg:Z,canvas:ca,vml:!Z&&!ca,product:"Highcharts 4.0.4",version:"/Highstock 2.0.4"})})();
|
381
domain-server/resources/web/stats/js/json.human.js
Executable file
381
domain-server/resources/web/stats/js/json.human.js
Executable file
|
@ -0,0 +1,381 @@
|
|||
/*globals define, module, require, document*/
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory);
|
||||
} else if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.JsonHuman = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
"use strict";
|
||||
|
||||
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
function makePrefixer(prefix) {
|
||||
return function (name) {
|
||||
return prefix + "-" + name;
|
||||
};
|
||||
}
|
||||
|
||||
function isArray(obj) {
|
||||
return toString.call(obj) === '[object Array]';
|
||||
}
|
||||
|
||||
function sn(tagName, className, data, keypath) {
|
||||
var result = document.createElement(tagName);
|
||||
|
||||
result.className = className;
|
||||
|
||||
if (keypath) {
|
||||
result.setAttribute('data-keypath', keypath);
|
||||
}
|
||||
|
||||
result.appendChild(document.createTextNode("" + data));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function scn(tagName, className, child) {
|
||||
var result = document.createElement(tagName),
|
||||
i, len;
|
||||
|
||||
result.className = className;
|
||||
|
||||
if (isArray(child)) {
|
||||
for (i = 0, len = child.length; i < len; i += 1) {
|
||||
result.appendChild(child[i]);
|
||||
}
|
||||
} else {
|
||||
result.appendChild(child);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function linkNode(child, href, target){
|
||||
var a = scn("a", HYPERLINK_CLASS_NAME, child);
|
||||
a.setAttribute('href', href);
|
||||
a.setAttribute('target', target);
|
||||
return a;
|
||||
}
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
prefixer = makePrefixer("jh"),
|
||||
p = prefixer,
|
||||
ARRAY = 1,
|
||||
BOOL = 2,
|
||||
INT = 3,
|
||||
FLOAT = 4,
|
||||
STRING = 5,
|
||||
OBJECT = 6,
|
||||
FUNCTION = 7,
|
||||
UNK = 99,
|
||||
|
||||
STRING_CLASS_NAME = p("type-string"),
|
||||
STRING_EMPTY_CLASS_NAME = p("type-string") + " " + p("empty"),
|
||||
|
||||
BOOL_TRUE_CLASS_NAME = p("type-bool-true"),
|
||||
BOOL_FALSE_CLASS_NAME = p("type-bool-false"),
|
||||
BOOL_IMAGE = p("type-bool-image"),
|
||||
INT_CLASS_NAME = p("type-int") + " " + p("type-number"),
|
||||
FLOAT_CLASS_NAME = p("type-float") + " " + p("type-number"),
|
||||
|
||||
OBJECT_CLASS_NAME = p("type-object"),
|
||||
OBJ_KEY_CLASS_NAME = p("key") + " " + p("object-key"),
|
||||
OBJ_VAL_CLASS_NAME = p("value") + " " + p("object-value"),
|
||||
OBJ_EMPTY_CLASS_NAME = p("type-object") + " " + p("empty"),
|
||||
|
||||
FUNCTION_CLASS_NAME = p("type-function"),
|
||||
|
||||
ARRAY_KEY_CLASS_NAME = p("key") + " " + p("array-key"),
|
||||
ARRAY_VAL_CLASS_NAME = p("value") + " " + p("array-value"),
|
||||
ARRAY_CLASS_NAME = p("type-array"),
|
||||
ARRAY_EMPTY_CLASS_NAME = p("type-array") + " " + p("empty"),
|
||||
|
||||
HYPERLINK_CLASS_NAME = p('a'),
|
||||
|
||||
UNKNOWN_CLASS_NAME = p("type-unk");
|
||||
|
||||
function getType(obj) {
|
||||
var type = typeof obj;
|
||||
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
return BOOL;
|
||||
case "string":
|
||||
return STRING;
|
||||
case "number":
|
||||
return (obj % 1 === 0) ? INT : FLOAT;
|
||||
case "function":
|
||||
return FUNCTION;
|
||||
default:
|
||||
if (isArray(obj)) {
|
||||
return ARRAY;
|
||||
} else if (obj === Object(obj)) {
|
||||
return OBJECT;
|
||||
} else {
|
||||
return UNK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _format(data, options, parentKey, keypath) {
|
||||
|
||||
var result, container, key, keyNode, valNode, len, childs, tr, value,
|
||||
isEmpty = true,
|
||||
accum = [],
|
||||
type = getType(data);
|
||||
|
||||
// Initialized & used only in case of objects & arrays
|
||||
var hyperlinksEnabled, aTarget, hyperlinkKeys ;
|
||||
|
||||
switch (type) {
|
||||
case BOOL:
|
||||
var boolOpt = options.bool;
|
||||
container = document.createElement('div');
|
||||
|
||||
if(boolOpt.showImage) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute('class', BOOL_IMAGE);
|
||||
|
||||
img.setAttribute('src',
|
||||
'' + (data ? boolOpt.img.true : boolOpt.img.false));
|
||||
|
||||
container.appendChild(img);
|
||||
}
|
||||
|
||||
if(boolOpt.showText){
|
||||
container.appendChild(data ?
|
||||
sn("span", BOOL_TRUE_CLASS_NAME, boolOpt.text.true, keypath) :
|
||||
sn("span", BOOL_FALSE_CLASS_NAME, boolOpt.text.false, keypath));
|
||||
}
|
||||
|
||||
result = container;
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
if (data === "") {
|
||||
result = sn("span", STRING_EMPTY_CLASS_NAME, "(Empty Text)", keypath);
|
||||
} else {
|
||||
result = sn("span", STRING_CLASS_NAME, data, keypath);
|
||||
}
|
||||
break;
|
||||
case INT:
|
||||
result = sn("span", INT_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
case FLOAT:
|
||||
result = sn("span", FLOAT_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
case OBJECT:
|
||||
childs = [];
|
||||
|
||||
aTarget = options.hyperlinks.target;
|
||||
hyperlinkKeys = options.hyperlinks.keys;
|
||||
|
||||
// Is Hyperlink Key
|
||||
hyperlinksEnabled =
|
||||
options.hyperlinks.enable &&
|
||||
hyperlinkKeys &&
|
||||
hyperlinkKeys.length > 0;
|
||||
|
||||
for (key in data) {
|
||||
isEmpty = false;
|
||||
|
||||
value = data[key];
|
||||
|
||||
var objectKeypath = keypath ? keypath + "." + key : key;
|
||||
|
||||
valNode = _format(value, options, key, objectKeypath);
|
||||
keyNode = sn("th", OBJ_KEY_CLASS_NAME, key);
|
||||
|
||||
if( hyperlinksEnabled &&
|
||||
typeof(value) === 'string' &&
|
||||
indexOf.call(hyperlinkKeys, key) >= 0){
|
||||
|
||||
valNode = scn("td", OBJ_VAL_CLASS_NAME, linkNode(valNode, value, aTarget));
|
||||
} else {
|
||||
valNode = scn("td", OBJ_VAL_CLASS_NAME, valNode);
|
||||
}
|
||||
|
||||
tr = document.createElement("tr");
|
||||
tr.appendChild(keyNode);
|
||||
tr.appendChild(valNode);
|
||||
|
||||
childs.push(tr);
|
||||
}
|
||||
|
||||
if (isEmpty) {
|
||||
result = sn("span", OBJ_EMPTY_CLASS_NAME, "(Empty Object)", keypath);
|
||||
} else {
|
||||
result = scn("table", OBJECT_CLASS_NAME, scn("tbody", '', childs));
|
||||
}
|
||||
break;
|
||||
case FUNCTION:
|
||||
result = sn("span", FUNCTION_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
case ARRAY:
|
||||
if (data.length > 0) {
|
||||
childs = [];
|
||||
var showArrayIndices = options.showArrayIndex;
|
||||
|
||||
aTarget = options.hyperlinks.target;
|
||||
hyperlinkKeys = options.hyperlinks.keys;
|
||||
|
||||
// Hyperlink of arrays?
|
||||
hyperlinksEnabled = parentKey && options.hyperlinks.enable &&
|
||||
hyperlinkKeys &&
|
||||
hyperlinkKeys.length > 0 &&
|
||||
indexOf.call(hyperlinkKeys, parentKey) >= 0;
|
||||
|
||||
for (key = 0, len = data.length; key < len; key += 1) {
|
||||
|
||||
keyNode = sn("th", ARRAY_KEY_CLASS_NAME, key);
|
||||
value = data[key];
|
||||
|
||||
var arrayKeypath = keypath + "[" + key + "]";
|
||||
|
||||
if(hyperlinksEnabled && typeof(value) === "string") {
|
||||
valNode = _format(value, options, key, arrayKeypath);
|
||||
valNode = scn("td", ARRAY_VAL_CLASS_NAME, linkNode(valNode, value, aTarget));
|
||||
} else {
|
||||
valNode = scn("td", ARRAY_VAL_CLASS_NAME, _format(value, options, key, arrayKeypath));
|
||||
}
|
||||
|
||||
tr = document.createElement("tr");
|
||||
|
||||
if(showArrayIndices) {
|
||||
tr.appendChild(keyNode);
|
||||
}
|
||||
tr.appendChild(valNode);
|
||||
|
||||
childs.push(tr);
|
||||
}
|
||||
|
||||
result = scn("table", ARRAY_CLASS_NAME, scn("tbody", '', childs));
|
||||
} else {
|
||||
result = sn("span", ARRAY_EMPTY_CLASS_NAME, "(Empty List)", keypath);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = sn("span", UNKNOWN_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function format(data, options) {
|
||||
options = validateOptions(options || {});
|
||||
|
||||
var result;
|
||||
|
||||
result = _format(data, options);
|
||||
result.className = result.className + " " + prefixer("root");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateOptions(options){
|
||||
options = validateArrayIndexOption(options);
|
||||
options = validateHyperlinkOptions(options);
|
||||
options = validateBoolOptions(options);
|
||||
|
||||
// Add any more option validators here
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
function validateArrayIndexOption(options) {
|
||||
if(options.showArrayIndex === undefined){
|
||||
options.showArrayIndex = true;
|
||||
} else {
|
||||
// Force to boolean just in case
|
||||
options.showArrayIndex = options.showArrayIndex ? true: false;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function validateHyperlinkOptions(options){
|
||||
var hyperlinks = {
|
||||
enable : false,
|
||||
keys : null,
|
||||
target : ''
|
||||
};
|
||||
|
||||
if(options.hyperlinks && options.hyperlinks.enable) {
|
||||
hyperlinks.enable = true;
|
||||
|
||||
hyperlinks.keys = isArray(options.hyperlinks.keys) ? options.hyperlinks.keys : [];
|
||||
|
||||
if(options.hyperlinks.target) {
|
||||
hyperlinks.target = '' + options.hyperlinks.target;
|
||||
} else {
|
||||
hyperlinks.target = '_blank';
|
||||
}
|
||||
}
|
||||
|
||||
options.hyperlinks = hyperlinks;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function validateBoolOptions(options){
|
||||
if(!options.bool){
|
||||
options.bool = {
|
||||
text: {
|
||||
true : "true",
|
||||
false : "false"
|
||||
},
|
||||
img : {
|
||||
true: "",
|
||||
false: ""
|
||||
},
|
||||
showImage : false,
|
||||
showText : true
|
||||
};
|
||||
} else {
|
||||
var boolOptions = options.bool;
|
||||
|
||||
// Show text if no option
|
||||
if(!boolOptions.showText && !boolOptions.showImage){
|
||||
boolOptions.showImage = false;
|
||||
boolOptions.showText = true;
|
||||
}
|
||||
|
||||
if(boolOptions.showText){
|
||||
if(!boolOptions.text){
|
||||
boolOptions.text = {
|
||||
true : "true",
|
||||
false : "false"
|
||||
};
|
||||
} else {
|
||||
var t = boolOptions.text.true, f = boolOptions.text.false;
|
||||
|
||||
if(getType(t) != STRING || t === ''){
|
||||
boolOptions.text.true = 'true';
|
||||
}
|
||||
|
||||
if(getType(f) != STRING || f === ''){
|
||||
boolOptions.text.false = 'false';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(boolOptions.showImage){
|
||||
if(!boolOptions.img.true && !boolOptions.img.false){
|
||||
boolOptions.showImage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
return {
|
||||
format: format
|
||||
};
|
||||
}));
|
|
@ -1,42 +1,99 @@
|
|||
function qs(key) {
|
||||
key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
|
||||
var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
var currentHighchart;
|
||||
|
||||
// setup a function to grab the nodeStats
|
||||
function getNodeStats() {
|
||||
|
||||
|
||||
var uuid = qs("uuid");
|
||||
|
||||
var statsTableBody = "";
|
||||
|
||||
|
||||
$.getJSON("/nodes/" + uuid + ".json", function(json){
|
||||
|
||||
|
||||
// update the table header with the right node type
|
||||
$('#stats-lead h3').html(json.node_type + " stats (" + uuid + ")");
|
||||
|
||||
|
||||
delete json.node_type;
|
||||
|
||||
$.each(json, function(key, value) {
|
||||
statsTableBody += "<tr>";
|
||||
statsTableBody += "<td class='stats-key'>" + key + "</td>";
|
||||
var formattedValue = (typeof value == 'number' ? value.toLocaleString() : value);
|
||||
statsTableBody += "<td>" + formattedValue + "</td>";
|
||||
statsTableBody += "</tr>";
|
||||
});
|
||||
|
||||
$('#stats-table tbody').html(statsTableBody);
|
||||
|
||||
var stats = JsonHuman.format(json);
|
||||
|
||||
$('#stats-container').html(stats);
|
||||
if (currentHighchart) {
|
||||
// get the current time to set with the point
|
||||
var x = (new Date()).getTime();
|
||||
|
||||
// get the last value using underscore-keypath
|
||||
var y = _(json).valueForKeyPath(graphKeypath);
|
||||
|
||||
// start shifting the chart once we hit 20 data points
|
||||
var shift = currentHighchart.series[0].data.length > 20;
|
||||
currentHighchart.series[0].addPoint([x, y], true, shift);
|
||||
}
|
||||
}).fail(function(data) {
|
||||
$('#stats-table td').each(function(){
|
||||
$('#stats-container th').each(function(){
|
||||
$(this).addClass('stale');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// do the first GET on page load
|
||||
getNodeStats();
|
||||
// grab the new assignments JSON every second
|
||||
var getNodeStatsInterval = setInterval(getNodeStats, 1000);
|
||||
|
||||
var graphKeypath = "";
|
||||
|
||||
// set the global Highcharts option
|
||||
Highcharts.setOptions({
|
||||
global: {
|
||||
useUTC: false
|
||||
}
|
||||
});
|
||||
|
||||
// add a function to help create the graph modal
|
||||
function createGraphModal() {
|
||||
var chartModal = bootbox.dialog({
|
||||
title: graphKeypath,
|
||||
message: "<div id='highchart-container'></div>",
|
||||
buttons: {},
|
||||
className: 'highchart-modal'
|
||||
});
|
||||
|
||||
chartModal.on('hidden.bs.modal', function(e) {
|
||||
currentHighchart.destroy();
|
||||
currentHighchart = null;
|
||||
});
|
||||
|
||||
currentHighchart = new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: 'highchart-container',
|
||||
defaultSeriesType: 'line'
|
||||
},
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
tickPixelInterval: 150
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: 'Value'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
series: [{
|
||||
name: graphKeypath,
|
||||
data: []
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
// handle clicks on numerical values - this lets the user show a line graph in a modal
|
||||
$('#stats-container').on('click', '.jh-type-number', function(){
|
||||
graphKeypath = $(this).data('keypath');
|
||||
|
||||
// setup the new graph modal
|
||||
createGraphModal();
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -39,29 +39,29 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
||||
|
||||
public slots:
|
||||
/// Called by NodeList to inform us a node has been added
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
/// Called by NodeList to inform us a node has been killed
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
void transactionJSONCallback(const QJsonObject& data);
|
||||
|
||||
|
||||
void restart();
|
||||
|
||||
|
||||
private slots:
|
||||
void loginFailed();
|
||||
void readAvailableDatagrams();
|
||||
void setupPendingAssignmentCredits();
|
||||
void sendPendingTransactionsToServer();
|
||||
|
||||
|
||||
void requestCurrentPublicSocketViaSTUN();
|
||||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void performICEUpdates();
|
||||
|
@ -72,23 +72,27 @@ private:
|
|||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool optionallySetupAssignmentPayment();
|
||||
|
||||
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool resetAccountManagerAccessToken();
|
||||
|
||||
void setupAutomaticNetworking();
|
||||
void sendHeartbeatToDataServer(const QString& networkAddress);
|
||||
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
void processICEHeartbeatResponse(const QByteArray& packet);
|
||||
|
||||
|
||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
unsigned int countConnectedUsers();
|
||||
bool verifyUsersKey (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn);
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
||||
const HifiSockAddr& senderSockAddr, QString& reasonReturn);
|
||||
|
||||
void preloadAllowedUserPublicKeys();
|
||||
void requestUserPublicKey(const QString& username);
|
||||
|
||||
|
||||
int parseNodeDataFromByteArray(QDataStream& packetStream,
|
||||
NodeType_t& nodeType,
|
||||
HifiSockAddr& publicSockAddr,
|
||||
|
@ -97,61 +101,63 @@ private:
|
|||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
||||
const NodeSet& nodeInterestList);
|
||||
|
||||
|
||||
void parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes);
|
||||
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
|
||||
void createStaticAssignmentsForType(Assignment::Type type, const QVariantList& configList);
|
||||
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
||||
void populateStaticScriptedAssignmentsFromSettings();
|
||||
|
||||
|
||||
SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
void respondToPathQuery(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
||||
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
|
||||
void handleProfileRequestFinished();
|
||||
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
|
||||
|
||||
|
||||
void loadExistingSessionsFromSettings();
|
||||
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||
TransactionHash _pendingAssignmentCredits;
|
||||
|
||||
|
||||
bool _isUsingDTLS;
|
||||
|
||||
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
|
||||
|
||||
QSet<QUuid> _webAuthenticationStateSet;
|
||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||
|
||||
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
|
||||
|
||||
QHash<QUuid, NetworkPeer> _connectingICEPeers;
|
||||
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
|
||||
|
||||
|
||||
QString _automaticNetworkingSetting;
|
||||
|
||||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#include <JSONBreakableMarshal.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
|
@ -31,16 +32,8 @@ DomainServerNodeData::DomainServerNodeData() :
|
|||
}
|
||||
|
||||
void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) {
|
||||
// push past the packet header
|
||||
QDataStream packetStream(statsPacket);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(statsPacket));
|
||||
|
||||
QVariantMap unpackedVariantMap;
|
||||
|
||||
packetStream >> unpackedVariantMap;
|
||||
|
||||
QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(unpackedVariantMap);
|
||||
_statsJSONObject = mergeJSONStatsFromNewObject(unpackedStatsJSON, _statsJSONObject);
|
||||
QVariantMap packetVariantMap = JSONBreakableMarshal::fromStringBuffer(statsPacket.mid(numBytesForPacketHeader(statsPacket)));
|
||||
_statsJSONObject = mergeJSONStatsFromNewObject(QJsonObject::fromVariantMap(packetVariantMap), _statsJSONObject);
|
||||
}
|
||||
|
||||
QJsonObject DomainServerNodeData::mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject) {
|
||||
|
|
|
@ -30,6 +30,9 @@ const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
|||
const QString SETTING_DEFAULT_KEY = "default";
|
||||
const QString DESCRIPTION_NAME_KEY = "name";
|
||||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||
_descriptionArray(),
|
||||
|
@ -38,31 +41,31 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
|||
// load the description object from the settings description
|
||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||
descriptionFile.open(QIODevice::ReadOnly);
|
||||
|
||||
|
||||
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
_configMap.loadMasterAndUserConfig(argumentList);
|
||||
|
||||
|
||||
// for now we perform a temporary transition from http-username and http-password to http_username and http_password
|
||||
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
|
||||
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
|
||||
|
||||
|
||||
if (oldUsername || oldPassword) {
|
||||
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data());
|
||||
|
||||
|
||||
// remove old keys, move to new format
|
||||
if (oldUsername) {
|
||||
settingsMap["http_username"] = oldUsername->toString();
|
||||
settingsMap.remove("http-username");
|
||||
}
|
||||
|
||||
|
||||
if (oldPassword) {
|
||||
settingsMap["http_password"] = oldPassword->toString();
|
||||
settingsMap.remove("http-password");
|
||||
}
|
||||
|
||||
|
||||
// save the updated settings
|
||||
persistToFile();
|
||||
}
|
||||
|
@ -70,18 +73,18 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
|
||||
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
|
||||
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
|
||||
|
||||
|
||||
if (foundValue) {
|
||||
return *foundValue;
|
||||
} else {
|
||||
int dotIndex = keyPath.indexOf('.');
|
||||
|
||||
|
||||
QString groupKey = keyPath.mid(0, dotIndex);
|
||||
QString settingKey = keyPath.mid(dotIndex + 1);
|
||||
|
||||
|
||||
foreach(const QVariant& group, _descriptionArray.toVariantList()) {
|
||||
QVariantMap groupMap = group.toMap();
|
||||
|
||||
|
||||
if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) {
|
||||
foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) {
|
||||
QVariantMap settingMap = setting.toMap();
|
||||
|
@ -89,187 +92,190 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
|
|||
return settingMap[SETTING_DEFAULT_KEY];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const QString SETTINGS_PATH = "/settings.json";
|
||||
|
||||
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a GET operation for our settings
|
||||
|
||||
|
||||
// check if there is a query parameter for settings affecting a particular type of assignment
|
||||
const QString SETTINGS_TYPE_QUERY_KEY = "type";
|
||||
QUrlQuery settingsQuery(url);
|
||||
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
|
||||
|
||||
|
||||
if (!typeValue.isEmpty()) {
|
||||
QJsonObject responseObject = responseObjectForType(typeValue);
|
||||
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
|
||||
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig(), _descriptionArray);
|
||||
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig());
|
||||
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
|
||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
|
||||
|
||||
return true;
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// setup a JSON Object with descriptions and non-omitted settings
|
||||
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
||||
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
||||
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
|
||||
|
||||
|
||||
QJsonObject rootObject;
|
||||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
|
||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
||||
|
||||
|
||||
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
|
||||
QJsonObject responseObject;
|
||||
|
||||
|
||||
if (!typeValue.isEmpty() || isAuthenticated) {
|
||||
// convert the string type value to a QJsonValue
|
||||
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());
|
||||
|
||||
|
||||
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
|
||||
|
||||
|
||||
// enumerate the groups in the description object to find which settings to pass
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
|
||||
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
|
||||
|
||||
QJsonObject groupResponseObject;
|
||||
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupSettingsArray) {
|
||||
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
|
||||
|
||||
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
|
||||
|
||||
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
|
||||
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
if (affectedTypesArray.isEmpty()) {
|
||||
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
}
|
||||
|
||||
|
||||
if (affectedTypesArray.contains(queryType) ||
|
||||
(queryType.isNull() && isAuthenticated)) {
|
||||
// this is a setting we should include in the responseObject
|
||||
|
||||
|
||||
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
|
||||
|
||||
|
||||
// we need to check if the settings map has a value for this setting
|
||||
QVariant variantValue;
|
||||
QVariant settingsMapGroupValue = _configMap.getMergedConfig()
|
||||
.value(groupObject[DESCRIPTION_NAME_KEY].toString());
|
||||
|
||||
if (!settingsMapGroupValue.isNull()) {
|
||||
variantValue = settingsMapGroupValue.toMap().value(settingName);
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey);
|
||||
|
||||
if (!settingsMapGroupValue.isNull()) {
|
||||
variantValue = settingsMapGroupValue.toMap().value(settingName);
|
||||
}
|
||||
} else {
|
||||
variantValue = _configMap.getMergedConfig().value(settingName);
|
||||
}
|
||||
|
||||
|
||||
QJsonValue result;
|
||||
|
||||
if (variantValue.isNull()) {
|
||||
// no value for this setting, pass the default
|
||||
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
||||
groupResponseObject[settingName] = settingObject[SETTING_DEFAULT_KEY];
|
||||
result = settingObject[SETTING_DEFAULT_KEY];
|
||||
} else {
|
||||
// users are allowed not to provide a default for string values
|
||||
// if so we set to the empty string
|
||||
groupResponseObject[settingName] = QString("");
|
||||
result = QString("");
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
groupResponseObject[settingName] = QJsonValue::fromVariant(variantValue);
|
||||
result = QJsonValue::fromVariant(variantValue);
|
||||
}
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
// this belongs in the group object
|
||||
groupResponseObject[settingName] = result;
|
||||
} else {
|
||||
// this is a value that should be at the root
|
||||
responseObject[settingName] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupResponseObject.isEmpty()) {
|
||||
|
||||
if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) {
|
||||
// set this group's object to the constructed object
|
||||
responseObject[groupKey] = groupResponseObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::settingExists(const QString& groupName, const QString& settingName,
|
||||
const QJsonArray& descriptionArray, QJsonValue& settingDescription) {
|
||||
foreach(const QJsonValue& groupValue, descriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
if (groupObject[DESCRIPTION_NAME_KEY].toString() == groupName) {
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
settingDescription = settingObject[SETTING_DEFAULT_KEY];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingDescription = QJsonValue::Undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
const QJsonValue& settingDescription) {
|
||||
const QJsonObject& settingDescription) {
|
||||
if (newValue.isString()) {
|
||||
if (newValue.toString().isEmpty()) {
|
||||
// this is an empty value, clear it in settings variant so the default is sent
|
||||
settingMap.remove(key);
|
||||
} else {
|
||||
// make sure the resulting json value has the right type
|
||||
const QString settingType = settingDescription.toObject()[SETTING_DESCRIPTION_TYPE_KEY].toString();
|
||||
QString settingType = settingDescription[SETTING_DESCRIPTION_TYPE_KEY].toString();
|
||||
const QString INPUT_DOUBLE_TYPE = "double";
|
||||
const QString INPUT_INTEGER_TYPE = "int";
|
||||
|
||||
|
||||
if (settingType == INPUT_DOUBLE_TYPE) {
|
||||
settingMap[key] = newValue.toString().toDouble();
|
||||
} else if (settingType == INPUT_INTEGER_TYPE) {
|
||||
settingMap[key] = newValue.toString().toInt();
|
||||
} else {
|
||||
settingMap[key] = newValue.toString();
|
||||
QString sanitizedValue = newValue.toString();
|
||||
|
||||
// we perform special handling for viewpoints here
|
||||
// we do not want them to be prepended with a slash
|
||||
if (key == SETTINGS_VIEWPOINT_KEY && !sanitizedValue.startsWith('/')) {
|
||||
sanitizedValue.prepend('/');
|
||||
}
|
||||
|
||||
settingMap[key] = sanitizedValue;
|
||||
}
|
||||
}
|
||||
} else if (newValue.isBool()) {
|
||||
|
@ -279,66 +285,158 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
// we don't have a map below this key yet, so set it up now
|
||||
settingMap[key] = QVariantMap();
|
||||
}
|
||||
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingMap[key].data());
|
||||
foreach(const QString childKey, newValue.toObject().keys()) {
|
||||
updateSetting(childKey, newValue.toObject()[childKey], thisMap, settingDescription.toObject()[key]);
|
||||
|
||||
QVariant& possibleMap = settingMap[key];
|
||||
|
||||
if (!possibleMap.canConvert(QMetaType::QVariantMap)) {
|
||||
// if this isn't a map then we need to make it one, otherwise we're about to crash
|
||||
qDebug() << "Value at" << key << "was not the expected QVariantMap while updating DS settings"
|
||||
<< "- removing existing value and making it a QVariantMap";
|
||||
possibleMap = QVariantMap();
|
||||
}
|
||||
|
||||
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(possibleMap.data());
|
||||
foreach(const QString childKey, newValue.toObject().keys()) {
|
||||
|
||||
QJsonObject childDescriptionObject = settingDescription;
|
||||
|
||||
// is this the key? if so we have the description already
|
||||
if (key != settingDescription[DESCRIPTION_NAME_KEY].toString()) {
|
||||
// otherwise find the description object for this childKey under columns
|
||||
foreach(const QJsonValue& column, settingDescription[DESCRIPTION_COLUMNS_KEY].toArray()) {
|
||||
if (column.isObject()) {
|
||||
QJsonObject thisDescription = column.toObject();
|
||||
if (thisDescription[DESCRIPTION_NAME_KEY] == childKey) {
|
||||
childDescriptionObject = column.toObject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString sanitizedKey = childKey;
|
||||
|
||||
if (key == SETTINGS_PATHS_KEY && !sanitizedKey.startsWith('/')) {
|
||||
// We perform special handling for paths here.
|
||||
// If we got sent a path without a leading slash then we add it.
|
||||
sanitizedKey.prepend("/");
|
||||
}
|
||||
|
||||
updateSetting(sanitizedKey, newValue.toObject()[childKey], thisMap, childDescriptionObject);
|
||||
}
|
||||
|
||||
if (settingMap[key].toMap().isEmpty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingMap.remove(key);
|
||||
}
|
||||
} else if (newValue.isArray()) {
|
||||
// we just assume array is replacement
|
||||
// TODO: we still need to recurse here with the description in case values in the array have special types
|
||||
settingMap[key] = newValue.toArray().toVariantList();
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant,
|
||||
const QJsonArray& descriptionArray) {
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& groupKey, postedObject.keys()) {
|
||||
QJsonValue groupValue = postedObject[groupKey];
|
||||
|
||||
if (!settingsVariant.contains(groupKey)) {
|
||||
// we don't have a map below this key yet, so set it up now
|
||||
settingsVariant[groupKey] = QVariantMap();
|
||||
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
||||
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
return settingObject;
|
||||
}
|
||||
|
||||
// Iterate on the settings
|
||||
foreach(const QString& settingKey, groupValue.toObject().keys()) {
|
||||
QJsonValue settingValue = groupValue.toObject()[settingKey];
|
||||
|
||||
QJsonValue thisDescription;
|
||||
if (settingExists(groupKey, settingKey, descriptionArray, thisDescription)) {
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[groupKey].data());
|
||||
updateSetting(settingKey, settingValue, thisMap, thisDescription);
|
||||
}
|
||||
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant) {
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& rootKey, postedObject.keys()) {
|
||||
QJsonValue rootValue = postedObject[rootKey];
|
||||
|
||||
if (!settingsVariant.contains(rootKey)) {
|
||||
// we don't have a map below this key yet, so set it up now
|
||||
settingsVariant[rootKey] = QVariantMap();
|
||||
}
|
||||
|
||||
QVariantMap* thisMap = &settingsVariant;
|
||||
|
||||
QJsonObject groupDescriptionObject;
|
||||
|
||||
// we need to check the description array to see if this is a root setting or a group setting
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
|
||||
// we matched a group - keep this since we'll use it below to update the settings
|
||||
groupDescriptionObject = groupValue.toObject();
|
||||
|
||||
// change the map we will update to be the map for this group
|
||||
thisMap = reinterpret_cast<QVariantMap*>(settingsVariant[rootKey].data());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsVariant[groupKey].toMap().empty()) {
|
||||
|
||||
if (groupDescriptionObject.isEmpty()) {
|
||||
// this is a root value, so we can call updateSetting for it directly
|
||||
// first we need to find our description value for it
|
||||
|
||||
QJsonObject matchingDescriptionObject;
|
||||
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
// find groups with root values (they don't have a group name)
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||
// this is a group with root values - check if our setting is in here
|
||||
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
|
||||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||
} else {
|
||||
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
||||
}
|
||||
} else {
|
||||
// this is a group - iterate on the settings in the group
|
||||
foreach(const QString& settingKey, rootValue.toObject().keys()) {
|
||||
// make sure this particular setting exists and we have a description object for it
|
||||
QJsonObject matchingDescriptionObject = settingDescriptionFromGroup(groupDescriptionObject, settingKey);
|
||||
|
||||
// if we matched the setting then update the value
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||
} else {
|
||||
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
||||
"- cannot update setting.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsVariant[rootKey].toMap().empty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingsVariant.remove(groupKey);
|
||||
settingsVariant.remove(rootKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::persistToFile() {
|
||||
|
||||
|
||||
// make sure we have the dir the settings file is supposed to live in
|
||||
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
||||
|
||||
|
||||
if (!settingsFileInfo.dir().exists()) {
|
||||
settingsFileInfo.dir().mkpath(".");
|
||||
}
|
||||
|
||||
|
||||
QFile settingsFile(_configMap.getUserConfigFilename());
|
||||
|
||||
|
||||
if (settingsFile.open(QIODevice::WriteOnly)) {
|
||||
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
|
||||
} else {
|
||||
qCritical("Could not write to JSON settings file. Unable to persist settings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,29 +18,34 @@
|
|||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPManager.h>
|
||||
|
||||
const QString SETTINGS_PATHS_KEY = "paths";
|
||||
|
||||
const QString SETTINGS_PATH = "/settings";
|
||||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServerSettingsManager();
|
||||
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
|
||||
void setupConfigMap(const QStringList& argumentList);
|
||||
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
|
||||
|
||||
|
||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
private:
|
||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
|
||||
const QJsonArray& descriptionArray);
|
||||
bool settingExists(const QString& groupName, const QString& settingName,
|
||||
const QJsonArray& descriptionArray, QJsonValue& settingDescription);
|
||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant);
|
||||
|
||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
const QJsonValue& settingDescription);
|
||||
const QJsonObject& settingDescription);
|
||||
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
||||
void persistToFile();
|
||||
|
||||
|
||||
QJsonArray _descriptionArray;
|
||||
HifiConfigVariantMap _configMap;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
|
|
|
@ -33,7 +33,7 @@ DomainServerWebSessionData::DomainServerWebSessionData(const QJsonObject& userOb
|
|||
}
|
||||
}
|
||||
|
||||
DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) {
|
||||
DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) : QObject() {
|
||||
_username = otherSessionData._username;
|
||||
_roles = otherSessionData._roles;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
//For procedural walk animation
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
Script.include("proceduralAnimationAPI.js");
|
||||
Script.include(HIFI_PUBLIC_BUCKET + "scripts/acScripts/proceduralAnimationAPI.js");
|
||||
|
||||
var procAnimAPI = new ProcAnimAPI();
|
||||
|
||||
|
|
163
examples/acScripts/rain.js
Normal file
163
examples/acScripts/rain.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// rain.js
|
||||
// examples
|
||||
//
|
||||
// Created by David Rowe on 9 Apr 2015.
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
var RainSquall = function (properties) {
|
||||
var // Properties
|
||||
squallOrigin,
|
||||
squallRadius,
|
||||
dropsPerMinute = 60,
|
||||
dropSize = { x: 0.1, y: 0.1, z: 0.1 },
|
||||
dropFallSpeed = 1, // m/s
|
||||
dropLifetime = 60, // Seconds
|
||||
dropSpinMax = 0, // Maximum angular velocity per axis; deg/s
|
||||
debug = false, // Display origin circle; don't use running on Stack Manager
|
||||
// Other
|
||||
squallCircle,
|
||||
SQUALL_CIRCLE_COLOR = { red: 255, green: 0, blue: 0 },
|
||||
SQUALL_CIRCLE_ALPHA = 0.5,
|
||||
SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0),
|
||||
raindropProperties,
|
||||
RAINDROP_MODEL_URL = "https://s3.amazonaws.com/hifi-public/ozan/Polyworld/Sets/sky/tetrahedron_v1-Faceted2.fbx",
|
||||
raindropTimer,
|
||||
raindrops = [], // HACK: Work around raindrops not always getting velocities
|
||||
raindropVelocities = [], // HACK: Work around raindrops not always getting velocities
|
||||
DEGREES_TO_RADIANS = Math.PI / 180;
|
||||
|
||||
function processProperties() {
|
||||
if (!properties.hasOwnProperty("origin")) {
|
||||
print("ERROR: Rain squall origin must be specified");
|
||||
return;
|
||||
}
|
||||
squallOrigin = properties.origin;
|
||||
|
||||
if (!properties.hasOwnProperty("radius")) {
|
||||
print("ERROR: Rain squall radius must be specified");
|
||||
return;
|
||||
}
|
||||
squallRadius = properties.radius;
|
||||
|
||||
if (properties.hasOwnProperty("dropsPerMinute")) {
|
||||
dropsPerMinute = properties.dropsPerMinute;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("dropSize")) {
|
||||
dropSize = properties.dropSize;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("dropFallSpeed")) {
|
||||
dropFallSpeed = properties.dropFallSpeed;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("dropLifetime")) {
|
||||
dropLifetime = properties.dropLifetime;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("dropSpinMax")) {
|
||||
dropSpinMax = properties.dropSpinMax;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("debug")) {
|
||||
debug = properties.debug;
|
||||
}
|
||||
|
||||
raindropProperties = {
|
||||
type: "Model",
|
||||
modelURL: RAINDROP_MODEL_URL,
|
||||
lifetime: dropLifetime,
|
||||
dimensions: dropSize,
|
||||
velocity: { x: 0, y: -dropFallSpeed, z: 0 },
|
||||
damping: 0,
|
||||
angularDamping: 0,
|
||||
ignoreForCollisions: true
|
||||
};
|
||||
}
|
||||
|
||||
function createRaindrop() {
|
||||
var angle,
|
||||
radius,
|
||||
offset,
|
||||
drop,
|
||||
spin = { x: 0, y: 0, z: 0 },
|
||||
maxSpinRadians = properties.dropSpinMax * DEGREES_TO_RADIANS,
|
||||
i;
|
||||
|
||||
// HACK: Work around rain drops not always getting velocities set at creation
|
||||
for (i = 0; i < raindrops.length; i += 1) {
|
||||
Entities.editEntity(raindrops[i], raindropVelocities[i]);
|
||||
}
|
||||
|
||||
angle = Math.random() * 360;
|
||||
radius = Math.random() * squallRadius;
|
||||
offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), { x: 0, y: -0.1, z: radius });
|
||||
raindropProperties.position = Vec3.sum(squallOrigin, offset);
|
||||
if (properties.dropSpinMax > 0) {
|
||||
spin = {
|
||||
x: Math.random() * maxSpinRadians,
|
||||
y: Math.random() * maxSpinRadians,
|
||||
z: Math.random() * maxSpinRadians
|
||||
};
|
||||
raindropProperties.angularVelocity = spin;
|
||||
}
|
||||
drop = Entities.addEntity(raindropProperties);
|
||||
|
||||
// HACK: Work around rain drops not always getting velocities set at creation
|
||||
raindrops.push(drop);
|
||||
raindropVelocities.push({
|
||||
velocity: raindropProperties.velocity,
|
||||
angularVelocity: spin
|
||||
});
|
||||
if (raindrops.length > 5) {
|
||||
raindrops.shift();
|
||||
raindropVelocities.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
if (debug) {
|
||||
squallCircle = Overlays.addOverlay("circle3d", {
|
||||
size: { x: 2 * squallRadius, y: 2 * squallRadius },
|
||||
color: SQUALL_CIRCLE_COLOR,
|
||||
alpha: SQUALL_CIRCLE_ALPHA,
|
||||
solid: true,
|
||||
visible: debug,
|
||||
position: squallOrigin,
|
||||
rotation: SQUALL_CIRCLE_ROTATION
|
||||
});
|
||||
}
|
||||
|
||||
raindropTimer = Script.setInterval(createRaindrop, 60000 / dropsPerMinute);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
Script.clearInterval(raindropTimer);
|
||||
if (debug) {
|
||||
Overlays.deleteOverlay(squallCircle);
|
||||
}
|
||||
}
|
||||
|
||||
processProperties();
|
||||
setUp();
|
||||
Script.scriptEnding.connect(tearDown);
|
||||
|
||||
return {
|
||||
};
|
||||
};
|
||||
|
||||
var rainSquall1 = new RainSquall({
|
||||
origin: { x: 1195, y: 1223, z: 1020 },
|
||||
radius: 25,
|
||||
dropsPerMinute: 120,
|
||||
dropSize: { x: 0.1, y: 0.1, z: 0.1 },
|
||||
dropFallSpeed: 3,
|
||||
dropLifetime: 30,
|
||||
dropSpinMax: 180,
|
||||
debug: false
|
||||
});
|
93
examples/ajt-test.js
Normal file
93
examples/ajt-test.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
|
||||
(function () {
|
||||
var spawnPoint = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
// constructor
|
||||
function TestBox() {
|
||||
this.entity = Entities.addEntity({ type: "Box",
|
||||
position: spawnPoint,
|
||||
dimentions: { x: 1, y: 1, z: 1 },
|
||||
color: { red: 100, green: 100, blue: 255 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
visible: true,
|
||||
locked: false,
|
||||
lifetime: 6000 });
|
||||
var self = this;
|
||||
this.timer = Script.setInterval(function () {
|
||||
var colorProp = { color: { red: Math.random() * 255,
|
||||
green: Math.random() * 255,
|
||||
blue: Math.random() * 255 } };
|
||||
Entities.editEntity(self.entity, colorProp);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
TestBox.prototype.Destroy = function () {
|
||||
Script.clearInterval(this.timer);
|
||||
Entities.editEntity(this.entity, { locked: false });
|
||||
Entities.deleteEntity(this.entity);
|
||||
}
|
||||
|
||||
// constructor
|
||||
function TestFx(color, emitDirection, emitRate, emitStrength, blinkRate) {
|
||||
var animationSettings = JSON.stringify({ fps: 30,
|
||||
frameIndex: 0,
|
||||
running: true,
|
||||
firstFrame: 0,
|
||||
lastFrame: 30,
|
||||
loop: true });
|
||||
|
||||
this.entity = Entities.addEntity({ type: "ParticleEffect",
|
||||
animationSettings: animationSettings,
|
||||
position: spawnPoint,
|
||||
textures: "http://www.hyperlogic.org/images/particle.png",
|
||||
emitRate: emitRate,
|
||||
emitStrength: emitStrength,
|
||||
emitDirection: emitDirection,
|
||||
color: color,
|
||||
visible: true,
|
||||
locked: false });
|
||||
|
||||
this.isPlaying = true;
|
||||
|
||||
var self = this;
|
||||
this.timer = Script.setInterval(function () {
|
||||
// flip is playing state
|
||||
self.isPlaying = !self.isPlaying;
|
||||
var animProp = { animationIsPlaying: self.isPlaying };
|
||||
Entities.editEntity(self.entity, animProp);
|
||||
}, (1 / blinkRate) * 1000);
|
||||
}
|
||||
|
||||
TestFx.prototype.Destroy = function () {
|
||||
Script.clearInterval(this.timer);
|
||||
Entities.editEntity(this.entity, { locked: false });
|
||||
Entities.deleteEntity(this.entity);
|
||||
}
|
||||
|
||||
var objs = [];
|
||||
function Init() {
|
||||
objs.push(new TestBox());
|
||||
objs.push(new TestFx({ red: 255, blue: 0, green: 0 },
|
||||
{ x: 0.5, y: 1.0, z: 0.0 },
|
||||
100, 3, 1));
|
||||
objs.push(new TestFx({ red: 0, blue: 255, green: 0 },
|
||||
{ x: 0, y: 1, z: 0 },
|
||||
1000, 5, 0.5));
|
||||
objs.push(new TestFx({ red: 0, blue: 0, green: 255 },
|
||||
{ x: -0.5, y: 1, z: 0 },
|
||||
100, 3, 1));
|
||||
}
|
||||
|
||||
function ShutDown() {
|
||||
var i, len = objs.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
objs[i].Destroy();
|
||||
}
|
||||
objs = [];
|
||||
}
|
||||
|
||||
Init();
|
||||
Script.scriptEnding.connect(ShutDown);
|
||||
})();
|
||||
|
||||
|
416
examples/avatarSelector.js
Normal file
416
examples/avatarSelector.js
Normal file
|
@ -0,0 +1,416 @@
|
|||
//
|
||||
// avatarSelector.js
|
||||
// examples
|
||||
//
|
||||
// Created by David Rowe on 21 Apr 2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Based on lobby.js created by Stephen Birarda on 17 Oct 2014.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var panelWall = false;
|
||||
var orbShell = false;
|
||||
var descriptionText = false;
|
||||
var showText = false;
|
||||
|
||||
// used for formating the description text, in meters
|
||||
var textWidth = 4;
|
||||
var textHeight = .5;
|
||||
var numberOfLines = 2;
|
||||
var textMargin = 0.0625;
|
||||
var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines;
|
||||
|
||||
var avatarStickPosition = {};
|
||||
|
||||
var orbNaturalExtentsMin = { x: -1.230354, y: -1.22077, z: -1.210487 };
|
||||
var orbNaturalExtentsMax = { x: 1.230353, y: 1.229819, z: 1.210487 };
|
||||
var panelsNaturalExtentsMin = { x: -1.223182, y: -0.348487, z: 0.0451369 };
|
||||
var panelsNaturalExtentsMax = { x: 1.223039, y: 0.602978, z: 1.224298 };
|
||||
|
||||
var orbNaturalDimensions = Vec3.subtract(orbNaturalExtentsMax, orbNaturalExtentsMin);
|
||||
var panelsNaturalDimensions = Vec3.subtract(panelsNaturalExtentsMax, panelsNaturalExtentsMin);
|
||||
|
||||
var SCALING_FACTOR = 10;
|
||||
var orbDimensions = Vec3.multiply(orbNaturalDimensions, SCALING_FACTOR);
|
||||
var panelsDimensions = Vec3.multiply(panelsNaturalDimensions, SCALING_FACTOR);
|
||||
|
||||
var orbNaturalCenter = Vec3.sum(orbNaturalExtentsMin, Vec3.multiply(orbNaturalDimensions, 0.5));
|
||||
var panelsNaturalCenter = Vec3.sum(panelsNaturalExtentsMin, Vec3.multiply(panelsNaturalDimensions, 0.5));
|
||||
var orbCenter = Vec3.multiply(orbNaturalCenter, SCALING_FACTOR);
|
||||
var panelsCenter = Vec3.multiply(panelsNaturalCenter, SCALING_FACTOR);
|
||||
var panelsCenterShift = Vec3.subtract(panelsCenter, orbCenter);
|
||||
|
||||
var ORB_SHIFT = { x: 0, y: -1.4, z: -0.8 };
|
||||
|
||||
var LOBBY_PANEL_WALL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/PanelWallForInterface.fbx";
|
||||
var LOBBY_BLANK_PANEL_TEXTURE_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/Texture.jpg";
|
||||
var LOBBY_SHELL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/LobbyShellForInterface.fbx";
|
||||
|
||||
var droneSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/drone.stereo.raw")
|
||||
var currentDrone = null;
|
||||
|
||||
var latinSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/latin.stereo.raw")
|
||||
var latinInjector = null;
|
||||
var elevatorSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/elevator.stereo.raw")
|
||||
var elevatorInjector = null;
|
||||
var currentMuzakInjector = null;
|
||||
var currentSound = null;
|
||||
|
||||
function textOverlayPosition() {
|
||||
var TEXT_DISTANCE_OUT = 6;
|
||||
var TEXT_DISTANCE_DOWN = -2;
|
||||
return Vec3.sum(Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), TEXT_DISTANCE_OUT)),
|
||||
Vec3.multiply(Quat.getUp(Camera.orientation), TEXT_DISTANCE_DOWN));
|
||||
}
|
||||
|
||||
var panelPlaceOrder = [
|
||||
7, 8, 9, 10, 11, 12, 13,
|
||||
0, 1, 2, 3, 4, 5, 6,
|
||||
14, 15, 16, 17, 18, 19, 20
|
||||
];
|
||||
|
||||
// Avatar index is 0-based
|
||||
function avatarIndexToPanelIndex(avatarIndex) {
|
||||
return panelPlaceOrder.indexOf(avatarIndex) + 1;
|
||||
}
|
||||
|
||||
// Panel index is 1-based
|
||||
function panelIndexToPlaceIndex(panelIndex) {
|
||||
return panelPlaceOrder[panelIndex - 1];
|
||||
}
|
||||
|
||||
var MAX_NUM_PANELS = 21;
|
||||
var DRONE_VOLUME = 0.3;
|
||||
|
||||
function drawLobby() {
|
||||
if (!panelWall) {
|
||||
print("Adding overlays for the avatar selector panel wall and orb shell.");
|
||||
|
||||
var cameraEuler = Quat.safeEulerAngles(Camera.orientation);
|
||||
var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0 });
|
||||
|
||||
var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT));
|
||||
|
||||
var panelWallProps = {
|
||||
url: LOBBY_PANEL_WALL_URL,
|
||||
position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)),
|
||||
rotation: towardsMe,
|
||||
dimensions: panelsDimensions
|
||||
};
|
||||
|
||||
var orbShellProps = {
|
||||
url: LOBBY_SHELL_URL,
|
||||
position: orbPosition,
|
||||
rotation: towardsMe,
|
||||
dimensions: orbDimensions,
|
||||
ignoreRayIntersection: true
|
||||
};
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
|
||||
var descriptionTextProps = {
|
||||
position: textOverlayPosition(),
|
||||
dimensions: { x: textWidth, y: textHeight },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
topMargin: textMargin,
|
||||
leftMargin: textMargin,
|
||||
bottomMargin: textMargin,
|
||||
rightMargin: textMargin,
|
||||
text: "",
|
||||
lineHeight: lineHeight,
|
||||
alpha: 0.9,
|
||||
backgroundAlpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
visible: false,
|
||||
isFacingAvatar: true
|
||||
};
|
||||
|
||||
avatarStickPosition = MyAvatar.position;
|
||||
|
||||
panelWall = Overlays.addOverlay("model", panelWallProps);
|
||||
orbShell = Overlays.addOverlay("model", orbShellProps);
|
||||
descriptionText = Overlays.addOverlay("text3d", descriptionTextProps);
|
||||
|
||||
if (droneSound.downloaded) {
|
||||
// start the drone sound
|
||||
if (!currentDrone) {
|
||||
currentDrone = Audio.playSound(droneSound, { stereo: true, loop: true, localOnly: true, volume: DRONE_VOLUME });
|
||||
} else {
|
||||
currentDrone.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// start one of our muzak sounds
|
||||
playRandomMuzak();
|
||||
}
|
||||
}
|
||||
|
||||
var avatars = {};
|
||||
|
||||
function changeLobbyTextures() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", "https://metaverse.highfidelity.com/api/v1/marketplace?category=head+%26+body&limit=21", false);
|
||||
req.send(); // Data returned is randomized.
|
||||
|
||||
avatars = JSON.parse(req.responseText).data.items;
|
||||
|
||||
var NUM_PANELS = avatars.length;
|
||||
|
||||
var textureProp = {
|
||||
textures: {}
|
||||
};
|
||||
|
||||
for (var j = 0; j < NUM_PANELS; j++) {
|
||||
var panelIndex = avatarIndexToPanelIndex(j);
|
||||
textureProp["textures"]["file" + panelIndex] = avatars[j].preview_url;
|
||||
};
|
||||
|
||||
Overlays.editOverlay(panelWall, textureProp);
|
||||
}
|
||||
|
||||
var MUZAK_VOLUME = 0.1;
|
||||
|
||||
function playCurrentSound(secondOffset) {
|
||||
if (currentSound == latinSound) {
|
||||
if (!latinInjector) {
|
||||
latinInjector = Audio.playSound(latinSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME });
|
||||
} else {
|
||||
latinInjector.restart();
|
||||
}
|
||||
|
||||
currentMuzakInjector = latinInjector;
|
||||
} else if (currentSound == elevatorSound) {
|
||||
if (!elevatorInjector) {
|
||||
elevatorInjector = Audio.playSound(elevatorSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME });
|
||||
} else {
|
||||
elevatorInjector.restart();
|
||||
}
|
||||
|
||||
currentMuzakInjector = elevatorInjector;
|
||||
}
|
||||
}
|
||||
|
||||
function playNextMuzak() {
|
||||
if (panelWall) {
|
||||
if (currentSound == latinSound) {
|
||||
if (elevatorSound.downloaded) {
|
||||
currentSound = elevatorSound;
|
||||
}
|
||||
} else if (currentSound == elevatorSound) {
|
||||
if (latinSound.downloaded) {
|
||||
currentSound = latinSound;
|
||||
}
|
||||
}
|
||||
|
||||
playCurrentSound(0);
|
||||
}
|
||||
}
|
||||
|
||||
function playRandomMuzak() {
|
||||
currentSound = null;
|
||||
|
||||
if (latinSound.downloaded && elevatorSound.downloaded) {
|
||||
currentSound = Math.random() < 0.5 ? latinSound : elevatorSound;
|
||||
} else if (latinSound.downloaded) {
|
||||
currentSound = latinSound;
|
||||
} else if (elevatorSound.downloaded) {
|
||||
currentSound = elevatorSound;
|
||||
}
|
||||
|
||||
if (currentSound) {
|
||||
// pick a random number of seconds from 0-10 to offset the muzak
|
||||
var secondOffset = Math.random() * 10;
|
||||
|
||||
playCurrentSound(secondOffset);
|
||||
} else {
|
||||
currentMuzakInjector = null;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupLobby() {
|
||||
toggleEnvironmentRendering(true);
|
||||
|
||||
// for each of the 21 placeholder textures, set them back to default so the cached model doesn't have changed textures
|
||||
var panelTexturesReset = {};
|
||||
panelTexturesReset["textures"] = {};
|
||||
|
||||
for (var j = 0; j < MAX_NUM_PANELS; j++) {
|
||||
panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL;
|
||||
};
|
||||
|
||||
Overlays.editOverlay(panelWall, panelTexturesReset);
|
||||
|
||||
Overlays.deleteOverlay(panelWall);
|
||||
Overlays.deleteOverlay(orbShell);
|
||||
Overlays.deleteOverlay(descriptionText);
|
||||
|
||||
panelWall = false;
|
||||
orbShell = false;
|
||||
|
||||
if (currentDrone) {
|
||||
currentDrone.stop();
|
||||
currentDrone = null
|
||||
}
|
||||
|
||||
if (currentMuzakInjector) {
|
||||
currentMuzakInjector.stop();
|
||||
currentMuzakInjector = null;
|
||||
}
|
||||
|
||||
avatars = {};
|
||||
|
||||
}
|
||||
|
||||
function actionStartEvent(event) {
|
||||
if (panelWall) {
|
||||
// we've got an action event and our panel wall is up
|
||||
// check if we hit a panel and if we should jump there
|
||||
var result = Overlays.findRayIntersection(event.actionRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
|
||||
var panelName = result.extraInfo;
|
||||
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
var panelIndex = parseInt(panelName.slice(5));
|
||||
var avatarIndex = panelIndexToPlaceIndex(panelIndex);
|
||||
if (avatarIndex < avatars.length) {
|
||||
var actionPlace = avatars[avatarIndex];
|
||||
|
||||
print("Changing avatar to " + actionPlace.name
|
||||
+ " after click on panel " + panelIndex + " with avatar index " + avatarIndex);
|
||||
|
||||
MyAvatar.useFullAvatarURL(actionPlace.content_url);
|
||||
|
||||
maybeCleanupLobby();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var control = false;
|
||||
|
||||
function keyPressEvent(event) {
|
||||
if (event.text === "CONTROL") {
|
||||
control = true;
|
||||
}
|
||||
|
||||
if (control && event.text === "a") {
|
||||
if (!panelWall) {
|
||||
toggleEnvironmentRendering(false);
|
||||
drawLobby();
|
||||
changeLobbyTextures();
|
||||
} else {
|
||||
cleanupLobby();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyReleaseEvent(event) {
|
||||
if (event.text === "CONTROL") {
|
||||
control = false;
|
||||
}
|
||||
}
|
||||
|
||||
var CLEANUP_EPSILON_DISTANCE = 0.05;
|
||||
|
||||
function maybeCleanupLobby() {
|
||||
if (panelWall && Vec3.length(Vec3.subtract(avatarStickPosition, MyAvatar.position)) > CLEANUP_EPSILON_DISTANCE) {
|
||||
cleanupLobby();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEnvironmentRendering(shouldRender) {
|
||||
Scene.shouldRenderAvatars = shouldRender;
|
||||
Scene.shouldRenderEntities = shouldRender;
|
||||
}
|
||||
|
||||
function handleLookAt(pickRay) {
|
||||
if (panelWall && descriptionText) {
|
||||
// we've got an action event and our panel wall is up
|
||||
// check if we hit a panel and if we should jump there
|
||||
var result = Overlays.findRayIntersection(pickRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
var panelName = result.extraInfo;
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
var panelIndex = parseInt(panelName.slice(5));
|
||||
var avatarIndex = panelIndexToPlaceIndex(panelIndex);
|
||||
if (avatarIndex < avatars.length) {
|
||||
var actionPlace = avatars[avatarIndex];
|
||||
|
||||
if (actionPlace.description == "") {
|
||||
Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText });
|
||||
} else {
|
||||
// handle line wrapping
|
||||
var allWords = actionPlace.description.split(" ");
|
||||
var currentGoodLine = "";
|
||||
var currentTestLine = "";
|
||||
var formatedDescription = "";
|
||||
var wordsFormated = 0;
|
||||
var currentTestWord = 0;
|
||||
var wordsOnLine = 0;
|
||||
while (wordsFormated < allWords.length) {
|
||||
// first add the "next word" to the line and test it.
|
||||
currentTestLine = currentGoodLine;
|
||||
if (wordsOnLine > 0) {
|
||||
currentTestLine += " " + allWords[currentTestWord];
|
||||
} else {
|
||||
currentTestLine = allWords[currentTestWord];
|
||||
}
|
||||
var lineLength = Overlays.textSize(descriptionText, currentTestLine).width;
|
||||
if (lineLength < textWidth || wordsOnLine == 0) {
|
||||
wordsFormated++;
|
||||
currentTestWord++;
|
||||
wordsOnLine++;
|
||||
currentGoodLine = currentTestLine;
|
||||
} else {
|
||||
formatedDescription += currentGoodLine + "\n";
|
||||
wordsOnLine = 0;
|
||||
currentGoodLine = "";
|
||||
currentTestLine = "";
|
||||
}
|
||||
}
|
||||
formatedDescription += currentGoodLine;
|
||||
Overlays.editOverlay(descriptionText, { text: formatedDescription, visible: showText });
|
||||
}
|
||||
} else {
|
||||
Overlays.editOverlay(descriptionText, { text: "", visible: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
maybeCleanupLobby();
|
||||
if (panelWall) {
|
||||
Overlays.editOverlay(descriptionText, { position: textOverlayPosition() });
|
||||
|
||||
// if the reticle is up then we may need to play the next muzak
|
||||
if (currentMuzakInjector && !currentMuzakInjector.isPlaying) {
|
||||
playNextMuzak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (panelWall) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
handleLookAt(pickRay);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.actionStartEvent.connect(actionStartEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(maybeCleanupLobby);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
|
@ -43,8 +43,8 @@ var floor = Entities.addEntity(
|
|||
var edge1 = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: Vec3.sum(center, { x: FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }),
|
||||
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE },
|
||||
color: { red: 128, green: 128, blue: 128 },
|
||||
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS },
|
||||
color: { red: 100, green: 100, blue: 100 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
visible: true,
|
||||
|
@ -54,8 +54,8 @@ var edge1 = Entities.addEntity(
|
|||
var edge2 = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: Vec3.sum(center, { x: -FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }),
|
||||
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE },
|
||||
color: { red: 128, green: 128, blue: 128 },
|
||||
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS },
|
||||
color: { red: 100, green: 100, blue: 100 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
visible: true,
|
||||
|
@ -65,8 +65,8 @@ var edge2 = Entities.addEntity(
|
|||
var edge3 = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: -FLOOR_SIZE / 2.0 }),
|
||||
dimensions: { x: FLOOR_SIZE, y: EDGE_THICKESS, z: EDGE_THICKESS },
|
||||
color: { red: 128, green: 128, blue: 128 },
|
||||
dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS },
|
||||
color: { red: 100, green: 100, blue: 100 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
visible: true,
|
||||
|
@ -76,8 +76,8 @@ var edge3 = Entities.addEntity(
|
|||
var edge4 = Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: FLOOR_SIZE / 2.0 }),
|
||||
dimensions: { x: FLOOR_SIZE, y: EDGE_THICKESS, z: EDGE_THICKESS },
|
||||
color: { red: 128, green: 128, blue: 128 },
|
||||
dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS },
|
||||
color: { red: 100, green: 100, blue: 100 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
visible: true,
|
||||
|
@ -97,6 +97,7 @@ for (var i = 0; i < NUM_BLOCKS; i++) {
|
|||
dimensions: { x: type.x * SCALE, y: type.y * SCALE, z: type.z * SCALE },
|
||||
color: { red: type.red, green: type.green, blue: type.blue },
|
||||
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||
velocity: { x: 0, y: 0.05, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
damping: DAMPING,
|
||||
lifetime: LIFETIME,
|
||||
|
@ -104,6 +105,11 @@ for (var i = 0; i < NUM_BLOCKS; i++) {
|
|||
}
|
||||
|
||||
function scriptEnding() {
|
||||
Entities.editEntity(edge1, { locked: false });
|
||||
Entities.editEntity(edge2, { locked: false });
|
||||
Entities.editEntity(edge3, { locked: false });
|
||||
Entities.editEntity(edge4, { locked: false });
|
||||
Entities.editEntity(floor, { locked: false });
|
||||
Entities.deleteEntity(edge1);
|
||||
Entities.deleteEntity(edge2);
|
||||
Entities.deleteEntity(edge3);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Brad Hefta-Gaub on 12/31/13.
|
||||
// Modified by Philip on 3/3/14
|
||||
// Modified by Thijs Wenker on 3/31/15
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that turns the hydra controllers and mouse into a entity gun.
|
||||
|
@ -66,7 +67,7 @@ var impactSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/BulletIm
|
|||
var targetHitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/hit.raw");
|
||||
var targetLaunchSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/shoot.raw");
|
||||
|
||||
var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst";
|
||||
var gunModel = "https://s3.amazonaws.com/hifi-public/cozza13/gun/m1911-handgun+1.fbx?v=4";
|
||||
|
||||
var audioOptions = {
|
||||
volume: 0.9
|
||||
|
@ -90,44 +91,49 @@ var score = 0;
|
|||
var bulletID = false;
|
||||
var targetID = false;
|
||||
|
||||
// Create a reticle image in center of screen
|
||||
// Create overlay buttons and reticle
|
||||
|
||||
var BUTTON_SIZE = 32;
|
||||
var PADDING = 3;
|
||||
var NUM_BUTTONS = 3;
|
||||
|
||||
var screenSize = Controller.getViewportDimensions();
|
||||
var startX = screenSize.x / 2 - (NUM_BUTTONS * (BUTTON_SIZE + PADDING)) / 2;
|
||||
var reticle = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - 16,
|
||||
y: screenSize.y / 2 - 16,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
x: screenSize.x / 2 - (BUTTON_SIZE / 2),
|
||||
y: screenSize.y / 2 - (BUTTON_SIZE / 2),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/crosshairs.svg",
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var offButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 96,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
x: startX,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/close.svg",
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
startX += BUTTON_SIZE + PADDING;
|
||||
var platformButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 130,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/city.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
x: startX,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/platform-targets.svg",
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
startX += BUTTON_SIZE + PADDING;
|
||||
var gridButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 164,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
x: startX,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/floating-targets.svg",
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
|
@ -163,7 +169,7 @@ function shootBullet(position, velocity, grenade) {
|
|||
{ type: "Sphere",
|
||||
position: position,
|
||||
dimensions: { x: bSize, y: bSize, z: bSize },
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
color: { red: 0, green: 0, blue: 0 },
|
||||
velocity: bVelocity,
|
||||
lifetime: BULLET_LIFETIME,
|
||||
gravity: { x: 0, y: bGravity, z: 0 },
|
||||
|
@ -260,6 +266,7 @@ function makeGrid(type, scale, size) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makePlatform(gravity, scale, size) {
|
||||
var separation = scale * 2;
|
||||
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
|
||||
|
@ -282,7 +289,7 @@ function makePlatform(gravity, scale, size) {
|
|||
z: pos.z - (separation * size / 2.0) + z * separation },
|
||||
dimensions: dimensions,
|
||||
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
|
||||
velocity: { x: 0, y: 0, z: 0 },
|
||||
velocity: { x: 0, y: 0.05, z: 0 },
|
||||
gravity: { x: 0, y: gravity, z: 0 },
|
||||
lifetime: TARGET_LIFE,
|
||||
damping: 0.1,
|
||||
|
@ -297,7 +304,7 @@ function makePlatform(gravity, scale, size) {
|
|||
type: "Box",
|
||||
position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z },
|
||||
dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size },
|
||||
color: { red: 128, green: 128, blue: 128 },
|
||||
color: { red: 100, green: 100, blue: 100 },
|
||||
lifetime: TARGET_LIFE
|
||||
});
|
||||
|
||||
|
@ -372,8 +379,8 @@ function takeFiringPose() {
|
|||
}
|
||||
}
|
||||
|
||||
MyAvatar.attach(gunModel, "RightHand", {x:0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(-0, -160, -79), 0.20);
|
||||
MyAvatar.attach(gunModel, "LeftHand", {x:-0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(0, 0, 79), 0.20);
|
||||
MyAvatar.attach(gunModel, "RightHand", {x:0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, -85, 79), 0.40);
|
||||
MyAvatar.attach(gunModel, "LeftHand", {x:-0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, 85, -79), 0.40);
|
||||
|
||||
// Give a bit of time to load before playing sound
|
||||
Script.setTimeout(playLoadSound, 2000);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -48,7 +48,7 @@ var rightHandEntity = false;
|
|||
var newSound = SoundCache.getSound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
|
||||
var catchSound = SoundCache.getSound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
|
||||
var throwSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw");
|
||||
var targetRadius = 1.0;
|
||||
var targetRadius = 0.25;
|
||||
|
||||
|
||||
var wantDebugging = false;
|
||||
|
@ -92,7 +92,6 @@ function checkControllerSide(whichSide) {
|
|||
Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[0]));
|
||||
linearVelocity = averageLinearVelocity[0];
|
||||
angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(LEFT_TIP));
|
||||
angularVelocity = Vec3.multiply(180.0 / Math.PI, angularVelocity);
|
||||
} else {
|
||||
BUTTON_FWD = RIGHT_BUTTON_FWD;
|
||||
BUTTON_3 = RIGHT_BUTTON_3;
|
||||
|
@ -104,7 +103,6 @@ function checkControllerSide(whichSide) {
|
|||
Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[1]));
|
||||
linearVelocity = averageLinearVelocity[1];
|
||||
angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(RIGHT_TIP));
|
||||
angularVelocity = Vec3.multiply(180.0 / Math.PI, angularVelocity);
|
||||
handMessage = "RIGHT";
|
||||
}
|
||||
|
||||
|
@ -122,30 +120,33 @@ function checkControllerSide(whichSide) {
|
|||
var closestEntity = Entities.findClosestEntity(palmPosition, targetRadius);
|
||||
|
||||
if (closestEntity.isKnownID) {
|
||||
var foundProperties = Entities.getEntityProperties(closestEntity);
|
||||
if (Vec3.length(foundProperties.velocity) > 0.0) {
|
||||
|
||||
debugPrint(handMessage + " HAND- CAUGHT SOMETHING!!");
|
||||
debugPrint(handMessage + " - Catching a moving object!");
|
||||
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftBallAlreadyInHand = true;
|
||||
leftHandEntity = closestEntity;
|
||||
} else {
|
||||
rightBallAlreadyInHand = true;
|
||||
rightHandEntity = closestEntity;
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftBallAlreadyInHand = true;
|
||||
leftHandEntity = closestEntity;
|
||||
} else {
|
||||
rightBallAlreadyInHand = true;
|
||||
rightHandEntity = closestEntity;
|
||||
}
|
||||
var ballPosition = getBallHoldPosition(whichSide);
|
||||
var properties = { position: { x: ballPosition.x,
|
||||
y: ballPosition.y,
|
||||
z: ballPosition.z },
|
||||
rotation: palmRotation,
|
||||
color: HELD_COLOR,
|
||||
velocity : { x: 0, y: 0, z: 0},
|
||||
gravity: { x: 0, y: 0, z: 0}
|
||||
};
|
||||
Entities.editEntity(closestEntity, properties);
|
||||
|
||||
Audio.playSound(catchSound, { position: ballPosition });
|
||||
|
||||
return; // exit early
|
||||
}
|
||||
var ballPosition = getBallHoldPosition(whichSide);
|
||||
var properties = { position: { x: ballPosition.x,
|
||||
y: ballPosition.y,
|
||||
z: ballPosition.z },
|
||||
rotation: palmRotation,
|
||||
color: HELD_COLOR,
|
||||
velocity : { x: 0, y: 0, z: 0},
|
||||
gravity: { x: 0, y: 0, z: 0}
|
||||
};
|
||||
Entities.editEntity(closestEntity, properties);
|
||||
|
||||
Audio.playSound(catchSound, { position: ballPosition });
|
||||
|
||||
return; // exit early
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ keyboard.onKeyRelease = function(event) {
|
|||
|
||||
var textLines = textText.split("\n");
|
||||
var maxLineWidth = Overlays.textSize(textSizeMeasureOverlay, textText).width;
|
||||
var usernameLine = "--" + GlobalServices.myUsername;
|
||||
var usernameLine = "--" + GlobalServices.username;
|
||||
var usernameWidth = Overlays.textSize(textSizeMeasureOverlay, usernameLine).width;
|
||||
if (maxLineWidth < usernameWidth) {
|
||||
maxLineWidth = usernameWidth;
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
//
|
||||
|
||||
Script.load("progress.js");
|
||||
Script.load("lookWithTouch.js");
|
||||
Script.load("editEntities.js");
|
||||
Script.load("edit.js");
|
||||
Script.load("selectAudioDevice.js");
|
||||
Script.load("controllers/hydra/hydraMove.js");
|
||||
Script.load("headMove.js");
|
||||
Script.load("inspect.js");
|
||||
Script.load("lobby.js");
|
||||
Script.load("notifications.js");
|
||||
Script.load("lookWithMouse.js")
|
||||
Script.load("look.js");
|
||||
Script.load("users.js");
|
||||
Script.load("grab.js");
|
||||
|
|
164
examples/dice.js
164
examples/dice.js
|
@ -12,100 +12,138 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var isDice = false;
|
||||
var NUMBER_OF_DICE = 2;
|
||||
var isDice = false;
|
||||
var NUMBER_OF_DICE = 4;
|
||||
var LIFETIME = 10000; // Dice will live for about 3 hours
|
||||
var dice = [];
|
||||
var DIE_SIZE = 0.20;
|
||||
|
||||
var madeSound = true; // Set false at start of throw to look for collision
|
||||
var madeSound = true; // Set false at start of throw to look for collision
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
SoundCache.getSound("http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav");
|
||||
|
||||
var rollSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/dice/diceRoll.wav");
|
||||
|
||||
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to create new objects."
|
||||
|
||||
var screenSize = Controller.getViewportDimensions();
|
||||
|
||||
var BUTTON_SIZE = 32;
|
||||
var PADDING = 3;
|
||||
|
||||
var offButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 96,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var deleteButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var diceButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 130,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/die.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
x: screenSize.x / 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/die.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var GRAVITY = -3.5;
|
||||
var LIFETIME = 300;
|
||||
|
||||
// NOTE: angularVelocity is in radians/sec
|
||||
var MAX_ANGULAR_SPEED = Math.PI;
|
||||
|
||||
|
||||
function shootDice(position, velocity) {
|
||||
if (!Entities.canRez()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
} else {
|
||||
for (var i = 0; i < NUMBER_OF_DICE; i++) {
|
||||
dice.push(Entities.addEntity(
|
||||
{ type: "Model",
|
||||
dice.push(Entities.addEntity(
|
||||
{
|
||||
type: "Model",
|
||||
modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx",
|
||||
position: position,
|
||||
velocity: velocity,
|
||||
position: position,
|
||||
velocity: velocity,
|
||||
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
|
||||
angularVelocity: { x: Math.random() * 100, y: Math.random() * 100, z: Math.random() * 100 },
|
||||
angularVelocity: {
|
||||
x: Math.random() * MAX_ANGULAR_SPEED,
|
||||
y: Math.random() * MAX_ANGULAR_SPEED,
|
||||
z: Math.random() * MAX_ANGULAR_SPEED
|
||||
},
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: GRAVITY,
|
||||
z: 0
|
||||
},
|
||||
lifetime: LIFETIME,
|
||||
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||
shapeType: "box",
|
||||
collisionsWillMove: true
|
||||
}));
|
||||
collisionsWillMove: true,
|
||||
collisionSoundURL: "http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav"
|
||||
}));
|
||||
position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDice() {
|
||||
while(dice.length > 0) {
|
||||
Entities.deleteEntity(dice.pop());
|
||||
}
|
||||
while (dice.length > 0) {
|
||||
Entities.deleteEntity(dice.pop());
|
||||
}
|
||||
}
|
||||
|
||||
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||
if (!madeSound) {
|
||||
// Is it one of our dice?
|
||||
for (var i = 0; i < dice.length; i++) {
|
||||
if (!dice[i].isKnownID) {
|
||||
dice[i] = Entities.identifyEntity(dice[i]);
|
||||
}
|
||||
if ((entity1.id == dice[i].id) || (entity2.id == dice[i].id)) {
|
||||
madeSound = true;
|
||||
Audio.playSound(rollSound, { position: collision.contactPoint });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
var clickedText = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
if (clickedOverlay == offButton) {
|
||||
deleteDice();
|
||||
} else if (clickedOverlay == diceButton) {
|
||||
var HOW_HARD = 2.0;
|
||||
var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
var velocity = Vec3.multiply(HOW_HARD, Quat.getFront(Camera.getOrientation()));
|
||||
shootDice(position, velocity);
|
||||
madeSound = false;
|
||||
}
|
||||
var clickedText = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
});
|
||||
if (clickedOverlay == offButton) {
|
||||
deleteDice();
|
||||
Script.stop();
|
||||
} else if (clickedOverlay == deleteButton) {
|
||||
deleteDice();
|
||||
} else if (clickedOverlay == diceButton) {
|
||||
var HOW_HARD = 2.0;
|
||||
var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
var velocity = Vec3.multiply(HOW_HARD, Quat.getFront(Camera.getOrientation()));
|
||||
shootDice(position, velocity);
|
||||
madeSound = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
deleteDice();
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(diceButton);
|
||||
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(diceButton);
|
||||
Overlays.deleteOverlay(deleteButton);
|
||||
}
|
||||
|
||||
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
File diff suppressed because it is too large
Load diff
100
examples/entityScripts/alternativeLightController.js
Normal file
100
examples/entityScripts/alternativeLightController.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
(function() {
|
||||
this.preload = function(entityId) {
|
||||
var soundURLs = ["https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_1.wav",
|
||||
"https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_2.wav",
|
||||
"https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_3.wav"];
|
||||
this.entityId = entityId;
|
||||
this.properties = Entities.getEntityProperties(this.entityId);
|
||||
this.previousPosition = this.properties.position;
|
||||
this.previousRotation = this.properties.rotation;
|
||||
this.getUserData()
|
||||
if (!this.userData) {
|
||||
this.userData = {};
|
||||
this.userData.lightOn = false;
|
||||
this.userData.soundIndex = Math.floor(Math.random() * soundURLs.length);
|
||||
this.updateUserData();
|
||||
}
|
||||
this.sound = SoundCache.getSound(soundURLs[this.userData.soundIndex]);
|
||||
}
|
||||
|
||||
|
||||
this.getUserData = function() {
|
||||
if (this.properties.userData) {
|
||||
this.userData = JSON.parse(this.properties.userData);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.updateUserData = function() {
|
||||
Entities.editEntity(this.entityId, {
|
||||
userData: JSON.stringify(this.userData)
|
||||
});
|
||||
}
|
||||
|
||||
this.clickReleaseOnEntity = function(entityId, mouseEvent) {
|
||||
if (!mouseEvent.isLeftButton) {
|
||||
return;
|
||||
}
|
||||
//first find closest light
|
||||
this.entityId = entityId
|
||||
this.playSound();
|
||||
this.properties = Entities.getEntityProperties(this.entityId)
|
||||
this.light = this.findClosestLight();
|
||||
if (this.light) {
|
||||
this.lightProperties = Entities.getEntityProperties(this.light);
|
||||
this.getUserData();
|
||||
Entities.editEntity(this.light, {
|
||||
visible: !this.userData.lightOn
|
||||
});
|
||||
|
||||
this.userData.lightOn = !this.userData.lightOn;
|
||||
this.updateUserData();
|
||||
this.tryMoveLight();
|
||||
}
|
||||
}
|
||||
|
||||
this.playSound = function() {
|
||||
if (this.sound && this.sound.downloaded) {
|
||||
Audio.playSound(this.sound, {
|
||||
position: this.properties.position,
|
||||
volume: 0.3
|
||||
});
|
||||
} else {
|
||||
print("Warning: Couldn't play sound.");
|
||||
}
|
||||
}
|
||||
|
||||
this.tryMoveLight = function() {
|
||||
if (this.light) {
|
||||
//compute offset position
|
||||
var offsetPosition = Quat.multiply(Quat.inverse(this.previousRotation), Vec3.subtract(this.lightProperties.position, this.previousPosition));
|
||||
var newPosition = Vec3.sum(this.properties.position, Vec3.multiplyQbyV(this.properties.rotation, offsetPosition));
|
||||
if (!Vec3.equal(newPosition, this.lightProperties.position)) {
|
||||
Entities.editEntity(this.light, {position: newPosition});
|
||||
}
|
||||
this.previousPosition = this.properties.position;
|
||||
this.previousRotation = this.properties.rotation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.findClosestLight = function() {
|
||||
var entities = Entities.findEntities(this.properties.position, 10);
|
||||
var lightEntities = [];
|
||||
var closestLight = null;
|
||||
var nearestDistance = 20
|
||||
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
var props = Entities.getEntityProperties(entities[i]);
|
||||
if (props.type === "Light") {
|
||||
var distance = Vec3.distance(props.position, this.properties.position)
|
||||
if (distance < nearestDistance) {
|
||||
closestLight = entities[i];
|
||||
nearestDistance = distance
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestLight;
|
||||
}
|
||||
|
||||
});
|
228
examples/entityScripts/lightController.js
Normal file
228
examples/entityScripts/lightController.js
Normal file
|
@ -0,0 +1,228 @@
|
|||
(function() {
|
||||
this.entityID = null;
|
||||
this.lightID = null;
|
||||
this.sound = null;
|
||||
this.soundURLs = ["https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_1.wav",
|
||||
"https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_2.wav",
|
||||
"https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_3.wav"]
|
||||
|
||||
var DEFAULT_USER_DATA = {
|
||||
creatingLight: false,
|
||||
lightID: null,
|
||||
lightDefaultProperties: {
|
||||
type: "Light",
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
dimensions: { x: 5, y: 5, z: 5 },
|
||||
isSpotlight: false,
|
||||
color: { red: 255, green: 48, blue: 0 },
|
||||
diffuseColor: { red: 255, green: 255, blue: 255 },
|
||||
ambientColor: { red: 255, green: 255, blue: 255 },
|
||||
specularColor: { red: 0, green: 0, blue: 0 },
|
||||
constantAttenuation: 1,
|
||||
linearAttenuation: 0,
|
||||
quadraticAttenuation: 0,
|
||||
intensity: 10,
|
||||
exponent: 0,
|
||||
cutoff: 180, // in degrees
|
||||
},
|
||||
soundIndex: Math.floor(Math.random() * this.soundURLs.length)
|
||||
};
|
||||
|
||||
function copyObject(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
function didEntityExist(entityID) {
|
||||
return entityID && entityID.isKnownID;
|
||||
}
|
||||
function doesEntityExistNow(entityID) {
|
||||
return entityID && getTrueID(entityID).isKnownID;
|
||||
}
|
||||
function getTrueID(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
return { id: properties.id, creatorTokenID: properties.creatorTokenID, isKnownID: properties.isKnownID };
|
||||
}
|
||||
function getUserData(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
if (properties.userData) {
|
||||
return JSON.parse(properties.userData);
|
||||
} else {
|
||||
print("Warning: light controller has no user data.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function updateUserData(entityID, userData) {
|
||||
Entities.editEntity(entityID, { userData: JSON.stringify(userData) });
|
||||
}
|
||||
|
||||
// Download sound if needed
|
||||
this.maybeDownloadSound = function() {
|
||||
if (this.sound === null) {
|
||||
var soundIndex = getUserData(this.entityID).soundIndex;
|
||||
this.sound = SoundCache.getSound(this.soundURLs[soundIndex]);
|
||||
}
|
||||
}
|
||||
// Play switch sound
|
||||
this.playSound = function() {
|
||||
if (this.sound && this.sound.downloaded) {
|
||||
Audio.playSound(this.sound, {
|
||||
position: Entities.getEntityProperties(this.entityID).position,
|
||||
volume: 0.2
|
||||
});
|
||||
} else {
|
||||
print("Warning: Couldn't play sound.");
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether the userData is well-formed and updates it if not
|
||||
this.checkUserData = function() {
|
||||
var userData = getUserData(this.entityID);
|
||||
if (!userData) {
|
||||
userData = DEFAULT_USER_DATA;
|
||||
} else if (!userData.lightDefaultProperties) {
|
||||
userData.lightDefaultProperties = DEFAULT_USER_DATA.lightDefaultProperties;
|
||||
} else if (!userData.soundIndex) {
|
||||
userData.soundIndex = DEFAULT_USER_DATA.soundIndex;
|
||||
}
|
||||
updateUserData(this.entityID, userData);
|
||||
}
|
||||
|
||||
// Create a Light entity
|
||||
this.createLight = function(userData) {
|
||||
var lightProperties = copyObject(userData.lightDefaultProperties);
|
||||
if (lightProperties) {
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID);
|
||||
|
||||
lightProperties.visible = false;
|
||||
lightProperties.position = Vec3.sum(entityProperties.position,
|
||||
Vec3.multiplyQbyV(entityProperties.rotation,
|
||||
lightProperties.position));
|
||||
return Entities.addEntity(lightProperties);
|
||||
} else {
|
||||
print("Warning: light controller has no default light.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to find a valid light, creates one otherwise
|
||||
this.updateLightID = function() {
|
||||
// Find valid light
|
||||
if (doesEntityExistNow(this.lightID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var userData = getUserData(this.entityID);
|
||||
if (doesEntityExistNow(userData.lightID)) {
|
||||
this.lightID = userData.lightID;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userData.creatingLight) {
|
||||
// No valid light, create one
|
||||
userData.creatingLight = true;
|
||||
updateUserData(this.entityID, userData);
|
||||
this.lightID = this.createLight(userData);
|
||||
this.maybeUpdateLightIDInUserData();
|
||||
print("Created new light entity");
|
||||
}
|
||||
}
|
||||
|
||||
this.maybeUpdateLightIDInUserData = function() {
|
||||
if (getTrueID(this.lightID).isKnownID) {
|
||||
this.lightID = getTrueID(this.lightID);
|
||||
this.updateLightIDInUserData();
|
||||
} else {
|
||||
var that = this;
|
||||
Script.setTimeout(function() { that.maybeUpdateLightIDInUserData() }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Update user data with new lightID
|
||||
this.updateLightIDInUserData = function() {
|
||||
var userData = getUserData(this.entityID);
|
||||
userData.lightID = this.lightID;
|
||||
userData.creatingLight = false;
|
||||
updateUserData(this.entityID, userData);
|
||||
}
|
||||
|
||||
// Moves light entity if the lamp entity moved
|
||||
this.maybeMoveLight = function() {
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID);
|
||||
var lightProperties = Entities.getEntityProperties(this.lightID);
|
||||
var lightDefaultProperties = getUserData(this.entityID).lightDefaultProperties;
|
||||
|
||||
var position = Vec3.sum(entityProperties.position,
|
||||
Vec3.multiplyQbyV(entityProperties.rotation,
|
||||
lightDefaultProperties.position));
|
||||
|
||||
if (!Vec3.equal(position, lightProperties.position)) {
|
||||
print("Lamp entity moved, moving light entity as well");
|
||||
Entities.editEntity(this.lightID, { position: position });
|
||||
}
|
||||
}
|
||||
|
||||
// Stores light entity relative position in the lamp metadata
|
||||
this.updateRelativeLightPosition = function() {
|
||||
if (!doesEntityExistNow(this.lightID)) {
|
||||
print("Warning: ID invalid, couldn't save relative position.");
|
||||
return;
|
||||
}
|
||||
|
||||
var userData = getUserData(this.entityID);
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID);
|
||||
var lightProperties = Entities.getEntityProperties(this.lightID);
|
||||
var newProperties = {};
|
||||
|
||||
// Copy only meaningful properties (trying to save space in userData here)
|
||||
for (var key in userData.lightDefaultProperties) {
|
||||
if (userData.lightDefaultProperties.hasOwnProperty(key)) {
|
||||
newProperties[key] = lightProperties[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Compute new relative position
|
||||
newProperties.position = Quat.multiply(Quat.inverse(entityProperties.rotation),
|
||||
Vec3.subtract(lightProperties.position,
|
||||
entityProperties.position));
|
||||
// inverse "visible" because right after we loaded the properties, the light entity is toggled.
|
||||
newProperties.visible = !lightProperties.visible;
|
||||
|
||||
userData.lightDefaultProperties = copyObject(newProperties);
|
||||
updateUserData(this.entityID, userData);
|
||||
print("Relative properties of light entity saved.");
|
||||
}
|
||||
|
||||
// This function should be called before any callback is executed
|
||||
this.preOperation = function(entityID) {
|
||||
this.entityID = entityID;
|
||||
|
||||
this.checkUserData();
|
||||
this.maybeDownloadSound();
|
||||
}
|
||||
|
||||
// Toggles the associated light entity
|
||||
this.toggleLight = function() {
|
||||
if (this.lightID) {
|
||||
var lightProperties = Entities.getEntityProperties(this.lightID);
|
||||
Entities.editEntity(this.lightID, { visible: !lightProperties.visible });
|
||||
this.playSound();
|
||||
} else {
|
||||
print("Warning: No light to turn on/off");
|
||||
}
|
||||
}
|
||||
|
||||
this.preload = function(entityID) {
|
||||
this.preOperation(entityID);
|
||||
}
|
||||
|
||||
this.clickReleaseOnEntity = function(entityID, mouseEvent) {
|
||||
this.preOperation(entityID);
|
||||
|
||||
if (mouseEvent.isLeftButton) {
|
||||
this.updateLightID();
|
||||
this.maybeMoveLight();
|
||||
this.toggleLight();
|
||||
} else if (mouseEvent.isRightButton) {
|
||||
this.updateRelativeLightPosition();
|
||||
}
|
||||
};
|
||||
})
|
|
@ -9,15 +9,20 @@
|
|||
|
||||
this.preload = function(entityID) {
|
||||
teleport = SoundCache.getSound("http://s3.amazonaws.com/hifi-public/birarda/teleport.raw");
|
||||
|
||||
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
portalDestination = properties.userData;
|
||||
animationURL = properties.modelURL;
|
||||
|
||||
print("The portal destination is " + portalDestination);
|
||||
}
|
||||
|
||||
this.enterEntity = function(entityID) {
|
||||
|
||||
var properties = Entities.getEntityProperties(entityID); // in case the userData/portalURL has changed
|
||||
portalDestination = properties.userData;
|
||||
|
||||
print("enterEntity() .... The portal destination is " + portalDestination);
|
||||
|
||||
if (portalDestination.length > 0) {
|
||||
print("Teleporting to hifi://" + portalDestination);
|
||||
Window.location = "hifi://" + portalDestination;
|
||||
|
|
71
examples/example/entities/changingAtmosphereExample.js
Normal file
71
examples/example/entities/changingAtmosphereExample.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// changingAtmosphereExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating a zone using the atmosphere features that changes scatter properties
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var count = 0;
|
||||
var stopAfter = 10000;
|
||||
|
||||
var zoneEntityA = Entities.addEntity({
|
||||
type: "Zone",
|
||||
position: { x: 1000, y: 1000, z: 1000},
|
||||
dimensions: { x: 2000, y: 2000, z: 2000 },
|
||||
keyLightColor: { red: 255, green: 0, blue: 0 },
|
||||
stageSunModelEnabled: false,
|
||||
shapeType: "sphere",
|
||||
backgroundMode: "atmosphere",
|
||||
atmosphere: {
|
||||
center: { x: 1000, y: 0, z: 1000},
|
||||
innerRadius: 1000.0,
|
||||
outerRadius: 1025.0,
|
||||
rayleighScattering: 0.0025, // Meaningful values 0 to ~0.01
|
||||
mieScattering: 0.0010, // Meaningful values 0 to ~0.01
|
||||
|
||||
// First two, Meaningful values 0 to 1 each, blue, purple; third meaningful 0.3 to 1 - affects shape
|
||||
scatteringWavelengths: { x: 0.650, y: 0.570, z: 0.475 },
|
||||
hasStars: true
|
||||
},
|
||||
stage: {
|
||||
latitude: 37.777,
|
||||
longitude: 122.407,
|
||||
altitude: 0.03,
|
||||
day: 183,
|
||||
hour: 5,
|
||||
sunModelEnabled: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(function(deltaTime) {
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
count++;
|
||||
var rayleighScattering = (count / 100000) % 0.01;
|
||||
var mieScattering = (count / 100000) % 0.01;
|
||||
var waveX = (count / 2000) % 1;
|
||||
var waveZ = ((count / 2000) % 0.7) + 0.3;
|
||||
|
||||
Entities.editEntity(zoneEntityA, {
|
||||
atmosphere: {
|
||||
rayleighScattering: rayleighScattering,
|
||||
mieScattering: mieScattering,
|
||||
scatteringWavelengths: { x: waveX, y: waveX, z: waveZ }
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
23
examples/example/entities/fullDomainZoneEntityExample.js
Normal file
23
examples/example/entities/fullDomainZoneEntityExample.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// fullDomainZoneEntityExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating a Zone which is as large as the entire domain
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Entities.addEntity({
|
||||
type: "Zone",
|
||||
position: { x: -10000, y: -10000, z: -10000 },
|
||||
dimensions: { x: 30000, y: 30000, z: 30000 },
|
||||
keyLightColor: { red: 255, green: 255, blue: 0 },
|
||||
keyLightDirection: { x: 0, y: -1.0, z: 0 },
|
||||
keyLightIntensity: 1.0,
|
||||
keyLightAmbientIntensity: 0.5
|
||||
});
|
||||
|
162
examples/example/entities/makeHouses.js
Normal file
162
examples/example/entities/makeHouses.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// makeHouses.js
|
||||
//
|
||||
//
|
||||
// Created by Stojce Slavkovski on March 14, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This sample script that creates house entities based on parameters.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function () {
|
||||
|
||||
/** options **/
|
||||
var numHouses = 100;
|
||||
var xRange = 300;
|
||||
var yRange = 300;
|
||||
|
||||
var sizeOfTheHouse = {
|
||||
x: 10,
|
||||
y: 15,
|
||||
z: 10
|
||||
};
|
||||
|
||||
var randomizeModels = false;
|
||||
/**/
|
||||
|
||||
var modelUrlPrefix = "http://public.highfidelity.io/load_testing/3-Buildings-2-SanFranciscoHouse-";
|
||||
var modelurlExt = ".fbx";
|
||||
var modelVariations = 100;
|
||||
|
||||
var houses = [];
|
||||
|
||||
function addHouseAt(position, rotation) {
|
||||
// get house model
|
||||
var modelNumber = randomizeModels ?
|
||||
1 + Math.floor(Math.random() * (modelVariations - 1)) :
|
||||
(houses.length + 1) % modelVariations;
|
||||
|
||||
if (modelNumber == 0) {
|
||||
modelNumber = modelVariations;
|
||||
}
|
||||
|
||||
var modelUrl = modelUrlPrefix + (modelNumber + "") + modelurlExt;
|
||||
print("Model ID:" + modelNumber);
|
||||
print("Model URL:" + modelUrl);
|
||||
|
||||
var properties = {
|
||||
type: "Model",
|
||||
position: position,
|
||||
rotation: rotation,
|
||||
dimensions: sizeOfTheHouse,
|
||||
modelURL: modelUrl
|
||||
};
|
||||
|
||||
return Entities.addEntity(properties);
|
||||
}
|
||||
|
||||
// calculate initial position
|
||||
var posX = MyAvatar.position.x - (xRange / 2);
|
||||
var measures = calculateParcels(numHouses, xRange, yRange);
|
||||
var dd = 0;
|
||||
|
||||
// avatar facing rotation
|
||||
var rotEven = Quat.fromPitchYawRollDegrees(0, 270.0 + MyAvatar.bodyYaw, 0.0);
|
||||
|
||||
// avatar opposite rotation
|
||||
var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0);
|
||||
var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation()));
|
||||
|
||||
var housePositions = []
|
||||
for (var j = 0; j < measures.rows; j++) {
|
||||
|
||||
var posX1 = 0 - (xRange / 2);
|
||||
dd += measures.parcelLength;
|
||||
|
||||
for (var i = 0; i < measures.cols; i++) {
|
||||
|
||||
// skip reminder of houses
|
||||
if (houses.length > numHouses) {
|
||||
break;
|
||||
}
|
||||
|
||||
var posShift = {
|
||||
x: posX1,
|
||||
y: 0,
|
||||
z: dd
|
||||
};
|
||||
|
||||
housePositions.push(Vec3.sum(housePos, posShift));
|
||||
posX1 += measures.parcelWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate rows and columns in area, and dimension of single parcel
|
||||
function calculateParcels(items, areaWidth, areaLength) {
|
||||
|
||||
var idealSize = Math.min(Math.sqrt(areaWidth * areaLength / items), areaWidth, areaLength);
|
||||
|
||||
var baseWidth = Math.min(Math.floor(areaWidth / idealSize), items);
|
||||
var baseLength = Math.min(Math.floor(areaLength / idealSize), items);
|
||||
|
||||
var sirRows = baseWidth;
|
||||
var sirCols = Math.ceil(items / sirRows);
|
||||
var sirW = areaWidth / sirRows;
|
||||
var sirL = areaLength / sirCols;
|
||||
|
||||
var visCols = baseLength;
|
||||
var visRows = Math.ceil(items / visCols);
|
||||
var visW = areaWidth / visRows;
|
||||
var visL = areaLength / visCols;
|
||||
|
||||
var rows = 0;
|
||||
var cols = 0;
|
||||
var parcelWidth = 0;
|
||||
var parcelLength = 0;
|
||||
|
||||
if (Math.min(sirW, sirL) > Math.min(visW, visL)) {
|
||||
rows = sirRows;
|
||||
cols = sirCols;
|
||||
parcelWidth = sirW;
|
||||
parcelLength = sirL;
|
||||
} else {
|
||||
rows = visRows;
|
||||
cols = visCols;
|
||||
parcelWidth = visW;
|
||||
parcelLength = visL;
|
||||
}
|
||||
|
||||
print("rows:" + rows);
|
||||
print("cols:" + cols);
|
||||
print("parcelWidth:" + parcelWidth);
|
||||
print("parcelLength:" + parcelLength);
|
||||
|
||||
return {
|
||||
rows: rows,
|
||||
cols: cols,
|
||||
parcelWidth: parcelWidth,
|
||||
parcelLength: parcelLength
|
||||
};
|
||||
}
|
||||
|
||||
var addHouses = function() {
|
||||
if (housePositions.length > 0) {
|
||||
position = housePositions.pop();
|
||||
print("House nr.:" + (houses.length + 1));
|
||||
houses.push(
|
||||
addHouseAt(position, (housePositions.length % 2 == 0) ? rotEven : rotOdd)
|
||||
);
|
||||
|
||||
// max 20 per second
|
||||
Script.setTimeout(addHouses, 50);
|
||||
} else {
|
||||
Script.stop();
|
||||
}
|
||||
};
|
||||
|
||||
addHouses();
|
||||
|
||||
})();
|
|
@ -11,11 +11,13 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var DEGREES_TO_RADIANS = Math.PI / 180.0;
|
||||
|
||||
var lightProperties = {
|
||||
type: "Light",
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
dimensions: { x: 1000, y: 1000, z: 1000 },
|
||||
angularVelocity: { x: 0, y: 10, z: 0 },
|
||||
angularVelocity: { x: 0, y: 10 * DEGREES_TO_RADIANS, z: 0 },
|
||||
angularDamping: 0,
|
||||
|
||||
isSpotlight: true,
|
||||
|
|
65
examples/example/entities/zoneAtmosphereExample.js
Normal file
65
examples/example/entities/zoneAtmosphereExample.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// zoneAtmosphereExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating a zone using the atmosphere features
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var count = 0;
|
||||
var stopAfter = 10000;
|
||||
|
||||
var zoneEntityA = Entities.addEntity({
|
||||
type: "Zone",
|
||||
position: { x: 1000, y: 1000, z: 1000},
|
||||
dimensions: { x: 2000, y: 2000, z: 2000 },
|
||||
keyLightColor: { red: 255, green: 0, blue: 0 },
|
||||
stageSunModelEnabled: false,
|
||||
shapeType: "sphere",
|
||||
backgroundMode: "atmosphere",
|
||||
atmosphere: {
|
||||
center: { x: 1000, y: 0, z: 1000},
|
||||
innerRadius: 1000.0,
|
||||
outerRadius: 1025.0,
|
||||
rayleighScattering: 0.0025,
|
||||
mieScattering: 0.0010,
|
||||
scatteringWavelengths: { x: 0.650, y: 0.570, z: 0.475 },
|
||||
hasStars: false
|
||||
},
|
||||
stage: {
|
||||
latitude: 37.777,
|
||||
longitude: 122.407,
|
||||
altitude: 0.03,
|
||||
day: 60,
|
||||
hour: 0,
|
||||
sunModelEnabled: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(function(deltaTime) {
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
count++;
|
||||
var newHour = (count / 10) % 24;
|
||||
var newIntensity = ((count / 10) % 24) / 24;
|
||||
print("newHour:" + newHour);
|
||||
print("newIntensity:" + newIntensity);
|
||||
|
||||
Entities.editEntity(zoneEntityA, {
|
||||
stageHour: newHour,
|
||||
keyLightIntensity: newIntensity
|
||||
});
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue