diff --git a/.gitattributes b/.gitattributes index 4efc7667f9..406780d20a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -25,3 +25,4 @@ *.svg binary *.ttf binary *.wav binary +*.rsrc binary diff --git a/.gitignore b/.gitignore index 0a2bcc6689..4a1c1c227b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ interface/external/*/* # Ignore interfaceCache for Linux users interface/interfaceCache/ -# ignore audio-client externals +# ignore audio-client externals libraries/audio-client/external/*/* !libraries/audio-client/external/*/readme.txt @@ -46,3 +46,7 @@ gvr-interface/libs/* # ignore files for various dev environments TAGS *.swp + +# ignore node files for the console +node_modules +npm-debug.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 0da8dc7309..0922779bc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ endif () if (POLICY CMP0043) cmake_policy(SET CMP0043 OLD) -endif () +endif () if (POLICY CMP0042) cmake_policy(SET CMP0042 OLD) @@ -26,24 +26,32 @@ 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") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") if (WIN32) add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) - # set path for Microsoft SDKs - # if get build error about missing 'glu32' this path is likely wrong - # Uncomment the line with 8.1 if running Windows 8.1 + + if (NOT WINDOW_SDK_PATH) + set(DEBUG_DISCOVERED_SDK_PATH TRUE) + endif() + + # sets path for Microsoft SDKs + # if you get build error about missing 'glu32' this path is likely wrong if (MSVC10) - set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ") + set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 " CACHE PATH "Windows SDK PATH") elseif (MSVC12) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(WINDOW_SDK_FOLDER "x64") + set(WINDOW_SDK_FOLDER "x64") else() - set(WINDOW_SDK_FOLDER "x86") + set(WINDOW_SDK_FOLDER "x86") endif() - set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}") + set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}" CACHE PATH "Windows SDK PATH") endif () - message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH}) + + if (DEBUG_DISCOVERED_SDK_PATH) + message(STATUS "The discovered Windows SDK path is ${WINDOW_SDK_PATH}") + endif () + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") @@ -75,7 +83,7 @@ if ((NOT MSVC12) AND (NOT MSVC14)) endif() endif () -if (APPLE) +if (APPLE) set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++0x") set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") @@ -85,16 +93,16 @@ if (NOT ANDROID_LIB_DIR) set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR}) endif () -if (ANDROID) +if (ANDROID) if (NOT ANDROID_QT_CMAKE_PREFIX_PATH) set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.4/android_armv7/lib/cmake) else () set(QT_CMAKE_PREFIX_PATH ${ANDROID_QT_CMAKE_PREFIX_PATH}) endif () - + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) - + if (ANDROID_LIB_DIR) list(APPEND CMAKE_FIND_ROOT_PATH ${ANDROID_LIB_DIR}) endif () @@ -107,6 +115,8 @@ else () endif () endif () +set(QT_DIR $ENV{QT_DIR}) + if (WIN32) if (NOT EXISTS ${QT_CMAKE_PREFIX_PATH}) message(FATAL_ERROR "Could not determine QT_CMAKE_PREFIX_PATH.") @@ -119,34 +129,34 @@ get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH}) if (APPLE) - + exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION) string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION}) message(STATUS "Detected OS X version = ${OSX_VERSION}") - + set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") - + # set our OS X deployment target set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8) - + # find the SDK path for the desired SDK find_path( - _OSX_DESIRED_SDK_PATH - NAME MacOSX${OSX_SDK}.sdk + _OSX_DESIRED_SDK_PATH + NAME MacOSX${OSX_SDK}.sdk HINTS ${OSX_SDK_PATH} PATHS /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ ) - + if (NOT _OSX_DESIRED_SDK_PATH) message(STATUS "Could not find OS X ${OSX_SDK} SDK. Will fall back to default. If you want a specific SDK, please pass OSX_SDK and optionally OSX_SDK_PATH to CMake.") else () message(STATUS "Found OS X ${OSX_SDK} SDK at ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk") - + # set that as the SDK to use set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk) endif () - + endif () # Hide automoc folders (for IDEs) @@ -170,9 +180,9 @@ else () set(UPPER_CMAKE_BUILD_TYPE DEBUG) endif () -set(HIFI_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(MACRO_DIR "${HIFI_CMAKE_DIR}/macros") -set(EXTERNAL_PROJECT_DIR "${HIFI_CMAKE_DIR}/externals") +set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(MACRO_DIR "${HF_CMAKE_DIR}/macros") +set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals") file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake") foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) @@ -198,9 +208,11 @@ if (WIN32) endif () if (NOT DEFINED SERVER_ONLY) - set(SERVER_ONLY 0) + set(SERVER_ONLY 0) endif() +set_packaging_parameters() + # add subdirectories for all targets if (NOT ANDROID) add_subdirectory(assignment-client) @@ -209,8 +221,7 @@ if (NOT ANDROID) set_target_properties(domain-server PROPERTIES FOLDER "Apps") add_subdirectory(ice-server) set_target_properties(ice-server PROPERTIES FOLDER "Apps") - add_subdirectory(stack-manager) - set_target_properties(stack-manager PROPERTIES FOLDER "Apps") + add_subdirectory(server-console) if (NOT SERVER_ONLY) add_subdirectory(interface) set_target_properties(interface PROPERTIES FOLDER "Apps") @@ -233,5 +244,4 @@ if (HIFI_MEMORY_DEBUGGING) endif (UNIX) endif () -include_application_version() -generate_installers() \ No newline at end of file +generate_installers() diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000000..701752f6af --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,25 @@ +Follow the [build guide](BUILD.md) to figure out how to build High Fidelity for your platform. + +During generation, CMake should produce an `install` target and a `package` target. + +###Install + +The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`. + +###Packaging + +To produce an installer, run the `package` target. + +####Windows + +To produce an executable installer on Windows, the following are required: + +- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3 +- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c +- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 + +Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. + +####OS X + +Run the `package` target to create an Apple Disk Image (.dmg). diff --git a/LICENSE b/LICENSE index 93c9a953d4..53c5ccf39a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,12 @@ -Copyright 2014 High Fidelity, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +Copyright (c) 2013-2016, High Fidelity, Inc. +All rights reserved. +licensing@highfidelity.io + +Licensed under the Apache License version 2.0 (the "License"); +You may not use this software except in compliance with the License. +You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 + +This software includes third-party software. +Please see each individual software license for additional details. + +This software is distributed "as-is" without any warranties, conditions, or representations whether express or implied, including without limitation the implied warranties and conditions of merchantability, merchantable quality, fitness for a particular purpose, performance, durability, title, non-infringement, and those arising from statute or from custom or usage of trade or course of dealing. diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 3064e97d70..1b5840c3c8 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -3,12 +3,14 @@ set(TARGET_NAME assignment-client) setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets) # link in the shared libraries -link_hifi_libraries( - audio avatars octree environment gpu model fbx entities +link_hifi_libraries( + audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver controllers physics ) -include_application_version() -package_libraries_for_deployment() -consolidate_stack_components() \ No newline at end of file +if (WIN32) + package_libraries_for_deployment() +endif() + +install_beside_console() diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index cddce5de7d..3a962d72d2 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include #include @@ -22,15 +22,13 @@ #include "AssignmentClient.h" #include "AssignmentClientMonitor.h" #include "AssignmentClientApp.h" +#include +#include AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : QCoreApplication(argc, argv) { - // to work around the Qt constant wireless scanning, set the env for polling interval very high - const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); - qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); - # ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); # endif @@ -42,10 +40,10 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : ShutdownEventListener::getInstance(); # endif - setOrganizationName("High Fidelity"); + setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); setOrganizationDomain("highfidelity.io"); setApplicationName("assignment-client"); - setApplicationName(BUILD_VERSION); + setApplicationVersion(BuildInfo::VERSION); // use the verbose message handler in Logging qInstallMessageHandler(LogHandler::verboseMessageHandler); @@ -63,7 +61,7 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : const QCommandLineOption poolOption(ASSIGNMENT_POOL_OPTION, "set assignment pool", "pool-name"); parser.addOption(poolOption); - + const QCommandLineOption portOption(ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION, "UDP port for this assignment client (or monitor)", "port"); parser.addOption(portOption); @@ -92,6 +90,12 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : const QCommandLineOption monitorPortOption(ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION, "assignment-client monitor port", "port"); parser.addOption(monitorPortOption); + const QCommandLineOption httpStatusPortOption(ASSIGNMENT_HTTP_STATUS_PORT, "http status server port", "http-status-port"); + parser.addOption(httpStatusPortOption); + + const QCommandLineOption logDirectoryOption(ASSIGNMENT_LOG_DIRECTORY, "directory to store logs", "log-directory"); + parser.addOption(logDirectoryOption); + if (!parser.parse(QCoreApplication::arguments())) { qCritical() << parser.errorText() << endl; parser.showHelp(); @@ -130,6 +134,18 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : numForks = minForks; } + quint16 httpStatusPort { 0 }; + if (parser.isSet(httpStatusPortOption)) { + httpStatusPort = parser.value(httpStatusPortOption).toUShort(); + } + + QDir logDirectory { "." }; + if (parser.isSet(logDirectoryOption)) { + logDirectory = parser.value(logDirectoryOption); + } else { + logDirectory = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + } + Assignment::Type requestAssignmentType = Assignment::AllTypes; if (argumentVariantMap.contains(ASSIGNMENT_TYPE_OVERRIDE_OPTION)) { requestAssignmentType = (Assignment::Type) argumentVariantMap.value(ASSIGNMENT_TYPE_OVERRIDE_OPTION).toInt(); @@ -168,11 +184,11 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : if (argumentVariantMap.contains(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) { assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toUInt(); } - + if (parser.isSet(assignmentServerPortOption)) { assignmentServerPort = parser.value(assignmentServerPortOption).toInt(); } - + // check for an overidden listen port quint16 listenPort = 0; if (argumentVariantMap.contains(ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION)) { @@ -200,7 +216,7 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks, requestAssignmentType, assignmentPool, listenPort, walletUUID, assignmentServerHostname, - assignmentServerPort); + assignmentServerPort, httpStatusPort, logDirectory); monitor->setParent(this); connect(this, &QCoreApplication::aboutToQuit, monitor, &AssignmentClientMonitor::aboutToQuit); } else { diff --git a/assignment-client/src/AssignmentClientApp.h b/assignment-client/src/AssignmentClientApp.h index c4705e30eb..37d3b9cc1d 100644 --- a/assignment-client/src/AssignmentClientApp.h +++ b/assignment-client/src/AssignmentClientApp.h @@ -25,6 +25,8 @@ 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"; +const QString ASSIGNMENT_HTTP_STATUS_PORT = "http-status-port"; +const QString ASSIGNMENT_LOG_DIRECTORY = "log-directory"; class AssignmentClientApp : public QCoreApplication { Q_OBJECT diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 2d27962071..f76434d9c7 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -12,6 +12,9 @@ #include #include +#include +#include + #include #include #include @@ -20,6 +23,7 @@ #include "AssignmentClientApp.h" #include "AssignmentClientChildData.h" #include "SharedUtil.h" +#include const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor"; const int WAIT_FOR_CHILD_MSECS = 1000; @@ -29,7 +33,9 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType, QString assignmentPool, quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, - quint16 assignmentServerPort) : + quint16 assignmentServerPort, quint16 httpStatusServerPort, QDir logDirectory) : + _logDirectory(logDirectory), + _httpManager(QHostAddress::LocalHost, httpStatusServerPort, "", this), _numAssignmentClientForks(numAssignmentClientForks), _minAssignmentClientForks(minAssignmentClientForks), _maxAssignmentClientForks(maxAssignmentClientForks), @@ -38,6 +44,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen _walletUUID(walletUUID), _assignmentServerHostname(assignmentServerHostname), _assignmentServerPort(assignmentServerPort) + { qDebug() << "_requestAssignmentType =" << _requestAssignmentType; @@ -80,24 +87,21 @@ void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) { } } -void AssignmentClientMonitor::childProcessFinished() { - QProcess* childProcess = qobject_cast(sender()); - qint64 processID = _childProcesses.key(childProcess); - - if (processID > 0) { - qDebug() << "Child process" << processID << "has finished. Removing from internal map."; - _childProcesses.remove(processID); +void AssignmentClientMonitor::childProcessFinished(qint64 pid) { + if (_childProcesses.remove(pid)) { + qDebug() << "Child process" << pid << "has finished. Removed from internal map."; } } void AssignmentClientMonitor::stopChildProcesses() { + qDebug() << "Stopping child processes"; auto nodeList = DependencyManager::get(); // ask child processes to terminate - foreach(QProcess* childProcess, _childProcesses) { - if (childProcess->processId() > 0) { - qDebug() << "Attempting to terminate child process" << childProcess->processId(); - childProcess->terminate(); + for (auto& ac : _childProcesses) { + if (ac.process->processId() > 0) { + qDebug() << "Attempting to terminate child process" << ac.process->processId(); + ac.process->terminate(); } } @@ -105,10 +109,10 @@ void AssignmentClientMonitor::stopChildProcesses() { if (_childProcesses.size() > 0) { // ask even more firmly - foreach(QProcess* childProcess, _childProcesses) { - if (childProcess->processId() > 0) { - qDebug() << "Attempting to kill child process" << childProcess->processId(); - childProcess->kill(); + for (auto& ac : _childProcesses) { + if (ac.process->processId() > 0) { + qDebug() << "Attempting to kill child process" << ac.process->processId(); + ac.process->kill(); } } @@ -155,18 +159,61 @@ void AssignmentClientMonitor::spawnChildClient() { _childArguments.append("--" + ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION); _childArguments.append(QString::number(DependencyManager::get()->getLocalSockAddr().getPort())); + // Setup log files + const QString DATETIME_FORMAT = "yyyyMMdd.hh.mm.ss.zzz"; + + if (!_logDirectory.exists()) { + qDebug() << "Log directory (" << _logDirectory.absolutePath() << ") does not exist, creating."; + _logDirectory.mkpath(_logDirectory.absolutePath()); + } + + auto nowString = QDateTime::currentDateTime().toString(DATETIME_FORMAT); + auto stdoutFilenameTemp = QString("ac-%1-stdout.txt").arg(nowString); + auto stderrFilenameTemp = QString("ac-%1-stderr.txt").arg(nowString); + QString stdoutPathTemp = _logDirectory.absoluteFilePath(stdoutFilenameTemp); + QString stderrPathTemp = _logDirectory.absoluteFilePath(stderrFilenameTemp); + + // reset our output and error files + assignmentClient->setStandardOutputFile(stdoutPathTemp); + assignmentClient->setStandardErrorFile(stderrPathTemp); + // make sure that the output from the child process appears in our output assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels); assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments); - + + // Update log path to use PID in filename + auto stdoutFilename = QString("ac-%1_%2-stdout.txt").arg(nowString).arg(assignmentClient->processId()); + auto stderrFilename = QString("ac-%1_%2-stderr.txt").arg(nowString).arg(assignmentClient->processId()); + QString stdoutPath = _logDirectory.absoluteFilePath(stdoutFilename); + QString stderrPath = _logDirectory.absoluteFilePath(stderrFilename); + + qDebug() << "Renaming " << stdoutPathTemp << " to " << stdoutPath; + if (!_logDirectory.rename(stdoutFilenameTemp, stdoutFilename)) { + qDebug() << "Failed to rename " << stdoutFilenameTemp; + stdoutPath = stdoutPathTemp; + stdoutFilename = stdoutFilenameTemp; + } + + qDebug() << "Renaming " << stderrPathTemp << " to " << stderrPath; + if (!QFile::rename(stderrPathTemp, stderrPath)) { + qDebug() << "Failed to rename " << stderrFilenameTemp; + stderrPath = stderrPathTemp; + stderrFilename = stderrFilenameTemp; + } + + qDebug() << "Child stdout being written to: " << stdoutFilename; + qDebug() << "Child stderr being written to: " << stderrFilename; + if (assignmentClient->processId() > 0) { + auto pid = assignmentClient->processId(); // make sure we hear that this process has finished when it does - connect(assignmentClient, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(childProcessFinished())); - + connect(assignmentClient, static_cast(&QProcess::finished), + this, [this, pid]() { childProcessFinished(pid); }); + qDebug() << "Spawned a child client with PID" << assignmentClient->processId(); - _childProcesses.insert(assignmentClient->processId(), assignmentClient); - } + _childProcesses.insert(assignmentClient->processId(), { assignmentClient, stdoutPath, stderrPath }); + } } void AssignmentClientMonitor::checkSpares() { @@ -222,12 +269,12 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer()->addOrUpdateNode (senderID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false); - + auto childData = std::unique_ptr { new AssignmentClientChildData(Assignment::Type::AllTypes) }; matchingNode->setLinkedData(std::move(childData)); @@ -252,10 +299,39 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointerreadPrimitive(&assignmentType); - - childData->setChildType((Assignment::Type) assignmentType); - + + childData->setChildType(Assignment::Type(assignmentType)); + // note when this child talked matchingNode->setLastHeardMicrostamp(usecTimestampNow()); } } + +bool AssignmentClientMonitor::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { + if (url.path() == "/status") { + QByteArray response; + + QJsonObject status; + QJsonObject servers; + for (auto& ac : _childProcesses) { + QJsonObject server; + + server["pid"] = ac.process->processId(); + server["logStdout"] = ac.logStdoutPath; + server["logStderr"] = ac.logStderrPath; + + servers[QString::number(ac.process->processId())] = server; + } + + status["servers"] = servers; + + QJsonDocument document { status }; + + connection->respond(HTTPConnection::StatusCode200, document.toJson()); + } else { + connection->respond(HTTPConnection::StatusCode404); + } + + + return true; +} diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index 7ea7c3e274..a5ae3cd1af 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -16,28 +16,39 @@ #include #include #include +#include #include #include "AssignmentClientChildData.h" +#include +#include extern const char* NUM_FORKS_PARAMETER; -class AssignmentClientMonitor : public QObject { +struct ACProcess { + QProcess* process; // looks like a dangling pointer, but is parented by the AssignmentClientMonitor + QString logStdoutPath; + QString logStderrPath; +}; + +class AssignmentClientMonitor : public QObject, public HTTPRequestHandler { Q_OBJECT public: AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks, const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType, QString assignmentPool, quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, - quint16 assignmentServerPort); + quint16 assignmentServerPort, quint16 httpStatusServerPort, QDir logDirectory); ~AssignmentClientMonitor(); void stopChildProcesses(); private slots: void checkSpares(); - void childProcessFinished(); + void childProcessFinished(qint64 pid); void handleChildStatusPacket(QSharedPointer message); - + + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; + public slots: void aboutToQuit(); @@ -47,6 +58,10 @@ private: QTimer _checkSparesTimer; // every few seconds see if it need fewer or more spare children + QDir _logDirectory; + + HTTPManager _httpManager; + const unsigned int _numAssignmentClientForks; const unsigned int _minAssignmentClientForks; const unsigned int _maxAssignmentClientForks; @@ -57,7 +72,7 @@ private: QString _assignmentServerHostname; quint16 _assignmentServerPort; - QMap _childProcesses; + QMap _childProcesses; }; #endif // hifi_AssignmentClientMonitor_h diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 2f9e7ee1e6..8ab53d3b87 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -24,6 +24,7 @@ #include "NodeType.h" #include "SendAssetTask.h" #include "UploadAssetTask.h" +#include const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; @@ -49,10 +50,42 @@ void AssetServer::run() { auto nodeList = DependencyManager::get(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); - _resourcesDirectory = QDir(QCoreApplication::applicationDirPath()).filePath("resources/assets"); - if (!_resourcesDirectory.exists()) { - qDebug() << "Creating resources directory"; - _resourcesDirectory.mkpath("."); + const QString RESOURCES_PATH = "assets"; + + _resourcesDirectory = QDir(ServerPathUtils::getDataDirectory()).filePath(RESOURCES_PATH); + + qDebug() << "Creating resources directory"; + _resourcesDirectory.mkpath("."); + + bool noExistingAssets = !_resourcesDirectory.exists() \ + || _resourcesDirectory.entryList(QDir::Files).size() == 0; + + if (noExistingAssets) { + qDebug() << "Asset resources directory not found, searching for existing asset resources"; + QString oldDataDirectory = QCoreApplication::applicationDirPath(); + auto oldResourcesDirectory = QDir(oldDataDirectory).filePath("resources/" + RESOURCES_PATH); + + + if (QDir(oldResourcesDirectory).exists()) { + qDebug() << "Existing assets found in " << oldResourcesDirectory << ", copying to " << _resourcesDirectory; + + + QDir resourcesParentDirectory = _resourcesDirectory.filePath(".."); + if (!resourcesParentDirectory.exists()) { + qDebug() << "Creating data directory " << resourcesParentDirectory.absolutePath(); + resourcesParentDirectory.mkpath("."); + } + + auto files = QDir(oldResourcesDirectory).entryList(QDir::Files); + + for (auto& file : files) { + auto from = oldResourcesDirectory + QDir::separator() + file; + auto to = _resourcesDirectory.absoluteFilePath(file); + qDebug() << "\tCopying from " << from << " to " << to; + QFile::copy(from, to); + } + + } } qDebug() << "Serving files from: " << _resourcesDirectory.path(); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 8fda0ead8e..bcd9478700 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -827,7 +827,12 @@ void AudioMixer::broadcastMixes() { } usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; // ns to us - + + if (usecToSleep > int(USECS_PER_SECOND)) { + qDebug() << "DANGER: amount to sleep is" << usecToSleep; + qDebug() << "NextFrame is" << nextFrame << "and timer nsecs elapsed is" << timer.nsecsElapsed(); + } + if (usecToSleep > 0) { usleep(usecToSleep); } diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 5d08848b8b..052c4755f8 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -9,11 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "AssignmentClientApp.h" - #include +#include + +#include "AssignmentClientApp.h" + int main(int argc, char* argv[]) { + disableQtBearerPoll(); // Fixes wifi ping spikes + AssignmentClientApp app(argc, argv); int acReturn = app.exec(); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 410aa922dc..d63e8e6620 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -27,6 +27,9 @@ #include "OctreeQueryNode.h" #include "OctreeServerConsts.h" +#include +#include +#include int OctreeServer::_clientCount = 0; const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000000; @@ -280,10 +283,10 @@ OctreeServer::~OctreeServer() { void OctreeServer::initHTTPManager(int port) { // setup the embedded web server - QString documentRoot = QString("%1/resources/web").arg(QCoreApplication::applicationDirPath()); + QString documentRoot = QString("%1/web").arg(ServerPathUtils::getDataDirectory()); // setup an httpManager with us as the request handler and the parent - _httpManager = new HTTPManager(port, documentRoot, this, this); + _httpManager = new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this, this); } bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { @@ -1031,6 +1034,7 @@ void OctreeServer::readConfiguration() { if (!readOptionString(QString("persistFilename"), settingsSectionObject, persistFilename)) { persistFilename = getMyDefaultPersistFilename(); } + strcpy(_persistFilename, qPrintable(persistFilename)); qDebug("persistFilename=%s", _persistFilename); @@ -1096,7 +1100,7 @@ void OctreeServer::run() { _tree->setIsServer(true); qDebug() << "Waiting for connection to domain to request settings from domain-server."; - + // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete); @@ -1139,9 +1143,52 @@ void OctreeServer::domainSettingsRequestComplete() { // if we want Persistence, set up the local file and persist thread if (_wantPersist) { + // If persist filename does not exist, let's see if there is one beside the application binary + // If there is, let's copy it over to our target persist directory + auto persistPath = ServerPathUtils::getDataFilePath("entities/" + QString(_persistFilename)); + if (!QFile::exists(persistPath)) { + qDebug() << "Persist file does not exist, checking for existence of persist file next to application"; + + static const QString OLD_DEFAULT_PERSIST_FILENAME = "resources/models.json.gz"; + QString oldResourcesDirectory = QCoreApplication::applicationDirPath(); + + // This is the old persist path, based on the current persist filename, which could + // be a custom filename set by the user. + auto oldPersistPath = QDir(oldResourcesDirectory).absoluteFilePath(_persistFilename); + + // This is the old default persist path. + auto oldDefaultPersistPath = QDir(oldResourcesDirectory).absoluteFilePath(OLD_DEFAULT_PERSIST_FILENAME); + + qDebug() << "Checking for existing persist file at " << oldPersistPath << " and " << oldDefaultPersistPath; + + QString pathToCopyFrom; + bool shouldCopy = false; + + if (QFile::exists(oldPersistPath)) { + shouldCopy = true; + pathToCopyFrom = oldPersistPath; + } else if (QFile::exists(oldDefaultPersistPath)) { + shouldCopy = true; + pathToCopyFrom = oldDefaultPersistPath; + } + + if (shouldCopy) { + qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << persistPath; + + QDir persistFileDirectory = QDir(persistPath).filePath(".."); + + if (!persistFileDirectory.exists()) { + qDebug() << "Creating data directory " << persistFileDirectory.path(); + persistFileDirectory.mkpath("."); + } + QFile::copy(pathToCopyFrom, persistPath); + } else { + qDebug() << "No existing persist file found"; + } + } // now set up PersistThread - _persistThread = new OctreePersistThread(_tree, _persistFilename, _persistInterval, + _persistThread = new OctreePersistThread(_tree, persistPath, _persistInterval, _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); _persistThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 2aabb97428..f1aa9531e8 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -21,7 +21,6 @@ #include #include -#include #include "OctreePersistThread.h" #include "OctreeSendThread.h" diff --git a/cmake/externals/faceshift/CMakeLists.txt b/cmake/externals/faceshift/CMakeLists.txt index b18b861912..28fbffec34 100644 --- a/cmake/externals/faceshift/CMakeLists.txt +++ b/cmake/externals/faceshift/CMakeLists.txt @@ -21,10 +21,23 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to Faceshift include directory") +set(LIBRARY_DEBUG_PATH "lib/Debug") +set(LIBRARY_RELEASE_PATH "lib/Release") + if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/faceshift.lib CACHE FILEPATH "Faceshift libraries") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/Release/faceshift.lib CACHE FILEPATH "Faceshift libraries") + set(LIBRARY_PREFIX "") + set(LIBRARY_EXT "lib") elseif (APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/libfaceshift.a CACHE FILEPATH "Faceshift libraries") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/Release/libfaceshift.a CACHE FILEPATH "Faceshift libraries") + set(LIBRARY_EXT "a") + set(LIBRARY_PREFIX "lib") + + if (CMAKE_GENERATOR STREQUAL "Unix Makefiles") + set(LIBRARY_DEBUG_PATH "build") + set(LIBRARY_RELEASE_PATH "build") + endif () endif() + +set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG + ${INSTALL_DIR}/${LIBRARY_DEBUG_PATH}/${LIBRARY_PREFIX}faceshift.${LIBRARY_EXT} CACHE FILEPATH "Faceshift libraries") +set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE +${INSTALL_DIR}/${LIBRARY_RELEASE_PATH}/${LIBRARY_PREFIX}faceshift.${LIBRARY_EXT} CACHE FILEPATH "Faceshift libraries") diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt deleted file mode 100644 index ac19e7d680..0000000000 --- a/cmake/externals/quazip/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -set(EXTERNAL_NAME quazip) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -cmake_policy(SET CMP0046 OLD) - -include(ExternalProject) - -if (WIN32) - # windows shell does not like backslashes expanded on the command line, - # so convert all backslashes in the QT path to forward slashes - string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -elseif ($ENV{QT_CMAKE_PREFIX_PATH}) - set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -endif () - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3-us-west-1.amazonaws.com/hifi-production/dependencies/quazip-0.6.2.zip - URL_MD5 514851970f1a14d815bdc3ad6267af4d - BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=/lib -DZLIB_ROOT=${ZLIB_ROOT} - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -add_dependencies(quazip zlib) - -# Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES - FOLDER "hidden/externals" - INSTALL_NAME_DIR ${INSTALL_DIR}/lib - BUILD_WITH_INSTALL_RPATH True) - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories") -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories") -set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL") - -if (APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library") -elseif (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip.lib CACHE FILEPATH "Location of QuaZip release library") -else () - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip.so CACHE FILEPATH "Location of QuaZip release library") -endif () - -include(SelectLibraryConfigurations) -select_library_configurations(${EXTERNAL_NAME_UPPER}) - -# Force selected libraries into the cache -set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries") -set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries") diff --git a/cmake/installer/install-folder.rsrc b/cmake/installer/install-folder.rsrc new file mode 100644 index 0000000000..8ce12afd3f --- /dev/null +++ b/cmake/installer/install-folder.rsrc @@ -0,0 +1,1634 @@ +data 'icns' (-16455) { + $"6963 6E73 0000 65EB 544F 4320 0000 0018" /* icns..eTOC .... */ + $"6974 3332 0000 25C3 7438 6D6B 0000 4008" /* it32..%t8mk..@. */ + $"6974 3332 0000 25C3 0000 0000 FF00 FF00" /* it32..%...... */ + $"FF00 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ........ */ + $"FF00 FF00 EE00 0005 A008 0007 D500 0309" /* .......... */ + $"549A BBA0 C102 C0A3 48D2 0003 2FB2 E4E3" /* T.H../ */ + $"A2DF 03E3 E176 01CF 0002 30C9 DDA5 D502" /* .v...0ݥ. */ + $"D7DE 61CE 0002 0EB1 D6A7 D002 D2CC 2BCD" /* a...֧.+ */ + $"0001 5BD4 A9CA 02D1 9A05 CB00 0202 9ACA" /* ..[ԩ.њ.... */ + $"AAC5 01CC 4FCB 0002 0EAF C1AA BF02 C2AF" /* .O....¯ */ + $"16CA 0002 14AE BCAA BA03 B9C1 7F02 C900" /* ........ */ + $"0213 A9B6 ACB4 02BA 6503 C800 0212 A4B1" /* ...e.... */ + $"ADAF 03B4 803B 26BA 2502 2415 0186 0002" /* .;&%.$.... */ + $"129F ABAE A902 AEAE AAB9 A905 AAA9 A184" /* ... */ + $"4003 8400 0211 9AA5 EFA4 04A5 A9AA 740E" /* @.....t. */ + $"8300 0211 949F 819E EC9D 049E 9EA6 7607" /* ....v. */ + $"8200 0710 8F9A 9997 9698 9CEA 9F05 9E9A" /* .... */ + $"9797 A04A 8200 080F 8A94 919C B5CE DDE4" /* J... */ + $"E9E5 06E2 D5BA 9F94 840B 8100 050F 848F" /* .պ.... */ + $"B2E1 F9EE FF04 F6D3 A78F 2881 0003 0E80" /* .ӧ(... */ + $"BEF6 F1FF 03FD DBA3 3881 0002 0BA1 F5F3" /* .ۣ8... */ + $"FF02 FECD 3C81 0001 16D1 F5FF 01ED 4781" /* .<....G */ + $"0001 36F1 F5FF 01F9 5281 0001 43F7 F5FF" /* ..6.R..C */ + $"01FB 5A81 0002 43F6 FFF3 FE02 FFFB 5F81" /* .Z..C._ */ + $"0002 43F5 FFF3 FD02 FEFA 5F81 0002 43F5" /* ..C._..C */ + $"FEB5 FC06 FDFE FEFF FEFE FDB4 FC02 FDF9" /* .. */ + $"5F81 0002 43F5 FEB0 FC00 FD81 FF06 FAF2" /* _..C.. */ + $"EDEB EEF4 FD81 FF00 FDAF FC02 FDF9 5F81" /* .._ */ + $"0002 43F4 FDAD FB16 FCFF FFF6 D1A0 704B" /* ..C.ѠpK */ + $"3222 1A18 1B26 3854 7CAD DDFD FFFF FCAC" /* 2"...&8T| */ + $"FB02 FCF8 5F81 0002 43F3 FCAB FA06 FCFF" /* ._..C. */ + $"FAC4 732E 068A 0006 0E3E 89D7 FFFF FBAA" /* s.....> */ + $"FA02 FBF8 5F81 0002 43F3 FBA9 F905 FAFF" /* ._..C. */ + $"F3A1 3C02 8300 0608 131B 1D19 1005 8300" /* <........... */ + $"040C 55BC FDFF A9F9 02FA F75F 8100 0243" /* ..U._..C */ + $"F3FB A7F9 04F8 FEFB A831 8200 0E26 5C91" /* .1..&\ */ + $"B9D4 E3EA ECE8 DFCE B083 4D19 8100 0501" /* ΰM.... */ + $"4DC7 FFFB F8A6 F902 FAF7 5F81 0002 43F2" /* M._..C */ + $"FAA7 F802 FFD5 4E81 0003 1D6E BFEF 80FF" /* .N...n */ + $"01FC FB80 FA08 FBFD FFFF FEE6 AC56 0D80" /* ..V. */ + $"0004 0973 EDFF F7A5 F802 F9F6 5F81 0002" /* ..s._.. */ + $"43F1 F8A4 F704 F6F8 FE9F 1580 0005 238F" /* C....# */ + $"E6FF FDF8 8AF7 05F9 FFFE D570 0F80 0002" /* .p... */ + $"31C6 FFA5 F702 F8F5 5F81 0002 43F0 F7A4" /* 1._..C */ + $"F602 F9F7 7280 0004 0C7C E8FF F990 F603" /* .r...|. */ + $"FBFF D357 8000 030F A1FF F7A3 F602 F7F5" /* W.... */ + $"5F81 0002 43F0 F7A3 F602 F9F2 5880 0003" /* _..C.X.. */ + $"37C8 FFF9 94F6 09FB FCA4 1900 0003 8AFE" /* 7.... */ + $"F7A2 F602 F7F5 5F81 0002 43F0 F6A2 F502" /* ._..C. */ + $"F8F2 5180 0002 63EE FC82 F501 F6F6 91F5" /* Q..c. */ + $"02FF D539 8000 0186 FDA2 F502 F6F4 5F81" /* .9..._ */ + $"0002 43EF F5A1 F408 F5F7 5D00 0001 7FFA" /* ..C.].... */ + $"F781 F405 F5FD F3F4 FDF5 90F4 07FB EA4F" /* ..O */ + $"0000 0194 FEA1 F402 F5F3 5E81 0002 43EE" /* ....^..C */ + $"F4A1 F301 FC7D 8000 0286 FCF4 81F3 07F5" /* .}... */ + $"EF81 3335 88F2 F490 F307 F7F0 5200 0008" /* 35.R... */ + $"B4FC A0F3 02F4 F25E 8100 0243 EEF3 A0F2" /* .^..C */ + $"07FB AE04 0000 77FB F382 F201 F86A 8100" /* ....w.j. */ + $"0177 F991 F207 F6ED 4200 001D DBF7 9FF2" /* .w.B... */ + $"02F3 F25E 8100 0243 EDF2 9FF1 07F5 DF20" /* .^..C. */ + $"0000 53F5 F382 F102 F5D8 0D81 0002 15E0" /* ..S..... */ + $"F486 F101 F2F2 86F1 07F7 DD25 0000 50F5" /* ..%..P */ + $"F29E F102 F2F1 5E81 0002 43EC F19F F006" /* .^..C. */ + $"F864 0000 25E1 F483 F002 F5CA 0481 0002" /* d..%.... */ + $"0AD4 F484 F005 F1F9 F4F5 F8F1 85F0 06F9" /* .. */ + $"B808 0000 A0F9 9EF0 02F1 F05E 8100 0243" /* ....^..C */ + $"EBF0 9EEF 06F6 BE06 0004 AEF8 84EF 02F1" /* ..... */ + $"E92C 8100 0237 EEF0 83EF 07F1 ED88 3E43" /* ,..7.>C */ + $"99F2 F085 EF06 FA74 0000 25E5 F19D EF02" /* .t..%. */ + $"F0EF 5E81 0002 43EB F09E EF05 F54B 0000" /* ^..C.K.. */ + $"59F7 86EF 07F5 BA20 0000 26C3 F584 EF01" /* Y. ..&. */ + $"F569 8100 0188 F785 EF06 F1E5 2500 0088" /* i...%.. */ + $"F99D EF02 F0EF 5E81 0002 43EB F09D EF06" /* .^..C. */ + $"F5BE 0500 0DCC F486 EF07 EEFA B400 13D3" /* ...... */ + $"F6EE 83EF 02F2 D20A 8100 021E E5F0 84EF" /* .... */ + $"07EE F898 0000 23E5 F09C EF02 F0EF 5E81" /* ...#.^ */ + $"0002 43EB EF9D EE06 F760 0000 61F7 ED87" /* ..C.`..a */ + $"EE05 F4BD 0018 DFEF 84EE 01F3 BF82 0002" /* ...... */ + $"0FD7 F085 EE07 EFE9 2A00 009D F6ED 9BEE" /* ..*.. */ + $"02EF EF5E 8100 0243 EAEE 9CED 06EF DB16" /* .^..C.. */ + $"0006 C0F3 88ED 05F3 BB00 17DD EF84 ED02" /* ...... */ + $"EFE0 1E81 0000 3988 ED05 F788 0000 46F2" /* ...9...F */ + $"9CED 02EE EE5E 8100 0243 E9ED 9CEC 05F4" /* .^..C. */ + $"9D00 003B EE89 EC05 F2BB 0017 DCEE 85EC" /* ..;... */ + $"07F3 A715 0000 20BF F187 EC06 EFD3 1000" /* .... ... */ + $"0DD0 EF9B EC02 EDED 5E81 0002 43E9 EC9C" /* ..^..C */ + $"EB05 F459 0000 86F5 89EB 05F1 BA00 17DB" /* .Y..... */ + $"ED86 EB05 F7A4 0016 CFF2 89EB 05F1 4900" /* ....I. */ + $"0096 F49B EB02 ECEC 5E81 0002 43E8 EB9B" /* ..^..C */ + $"EA06 EBE4 2500 05C2 EF89 EA05 F0B9 0017" /* .%..... */ + $"DAEC 86EA 05F1 B300 1DE1 EC89 EA05 F48B" /* .... */ + $"0000 5AF3 9BEA 02EB EC5E 8100 0243 E7EA" /* ..Z.^..C */ + $"9BE9 06ED C607 0024 E3EA 89E9 05EF B800" /* ...$.. */ + $"18E1 ED86 E905 F0B0 001C DFEB 89E9 06EE" /* ..... */ + $"BE04 002D E7EA 9AE9 02EA EB5E 8100 0243" /* ..-.^..C */ + $"E7EA 9BE9 05F1 A400 004B F08A E907 EFB8" /* ...K. */ + $"000A 8AE0 F3EC 84E9 05F0 B000 1CDF EB89" /* .Š... */ + $"E906 EBDC 1A00 11D3 EC9A E902 EAEB 5E81" /* .....^ */ + $"0002 43E7 E99B E805 F284 0000 6FF2 8AE8" /* ..C...o */ + $"01EE B780 0004 2481 D6F2 EC82 E805 EFAF" /* ...$. */ + $"001C DEEA 89E8 06E9 EA35 0002 BCEE 9AE8" /* ...5.. */ + $"02E9 EA5E 8100 0243 E6E8 9BE7 05F1 6B00" /* .^..C.k. */ + $"008A F18A E70B EDB7 0003 0200 0019 70CB" /* ........p */ + $"F0ED 80E7 05EE AF00 1CDD E98A E705 EF4E" /* ....N */ + $"0000 A7EF 9AE7 02E8 E95E 8100 0243 E5E7" /* ...^..C */ + $"9BE6 05EF 5B00 009B EF8A E614 ECB6 0017" /* .[..... */ + $"A959 0B00 0010 60BF EDED E7ED AE00 1CDC" /* Y....`.. */ + $"E88A E605 F05F 0000 97EF 9AE6 02E7 E95E" /* ._...^ */ + $"8100 0243 E4E6 9BE5 05ED 5300 00A3 ED8A" /* ..C.S.. */ + $"E514 EBB5 0016 DCEF C367 1400 0008 50B1" /* ...g....P */ + $"E9F4 AE00 1CDB E78A E505 EF68 0000 8FEE" /* ...h.. */ + $"9AE5 02E6 E85E 8100 0243 E4E5 9BE4 05EC" /* .^..C. */ + $"5200 00A3 EC8A E414 EAB4 0016 D5E7 E9EE" /* R..... */ + $"CD76 1D00 0002 42A9 A900 1CDA E68A E405" /* v....B... */ + $"EF68 0000 8EEE 9AE4 02E5 E75E 8100 0243" /* h...^..C */ + $"E3E4 9BE3 05EC 5900 009A EC8A E30C E9B3" /* .Y... */ + $"0016 D4E5 E3E3 E7ED D585 2880 0004 1E00" /* ..Յ(.... */ + $"1CD9 E58A E305 ED5F 0000 95EC 9AE3 02E4" /* .._... */ + $"E65E 8100 0243 E3E4 9BE3 05ED 6800 0089" /* ^..C.h.. */ + $"EC8A E305 E9B3 0016 D4E5 81E3 04E5 EDDC" /* .... */ + $"9435 8000 021C D9E5 8AE3 05EB 4E00 00A3" /* 5....N.. */ + $"EA9A E302 E4E6 5E81 0002 43E3 E49B E305" /* .^..C. */ + $"EC80 0000 6FED 8AE3 05E9 B300 16D4 E583" /* ..o... */ + $"E308 E4EC E2A4 3B00 1CD9 E58A E305 E536" /* .;...6 */ + $"0002 B6E8 9AE3 02E4 E65E 8100 0243 E2E3" /* ...^..C */ + $"9BE2 05EA 9D00 004B E98A E205 E8B3 0016" /* ...K... */ + $"D3E4 85E2 06E3 EFAF 001B D8E4 89E2 06E4" /* .... */ + $"D61A 000F CCE5 9AE2 02E3 E65D 8100 0243" /* ....]..C */ + $"E2E2 9BE1 06E5 BE06 0025 DCE2 89E1 05E7" /* ...%. */ + $"B200 16D2 E386 E105 E7AA 001B D7E3 89E1" /* ..... */ + $"06E6 B904 002A DEE2 9AE1 02E2 E55D 8100" /* ...*.]. */ + $"0243 E1E1 9BE0 06E1 D921 0006 BCE4 89E0" /* .C.!.. */ + $"05E6 B200 16D3 E286 E005 E6A9 001B D6E2" /* ...... */ + $"89E0 05E9 8800 0054 E89B E002 E1E4 5D81" /* ...T.] */ + $"0002 43E0 E09C DF05 E751 0000 83E9 89DF" /* ..C.Q.. */ + $"05EA A900 12C6 E686 DF05 E5A8 001B D5E1" /* ...... */ + $"89DF 05E6 4900 008B E89B DF02 E0E3 5D81" /* .I...] */ + $"0002 43DF DF9C DE05 E691 0000 3BE2 88DE" /* ..Cߜ...; */ + $"07E4 AF1F 0000 25B7 E385 DE05 E4A8 001B" /* ....%... */ + $"D4E0 88DE 06E1 C911 000A C1E2 9BDE 02DF" /* .... */ + $"E35D 8100 0243 DFDE 9CDD 06E0 CA12 0007" /* ]..Cޜ.... */ + $"B7E2 86DD 02DE D82A 8100 0234 DCDE 84DD" /* .*..4ބ */ + $"05E3 A700 1BD3 DF88 DD05 E783 0000 3DE1" /* ...߈...= */ + $"9DDD 01E2 5D81 0002 43DE DD9D DC05 E454" /* .]..Cݝ.T */ + $"0000 5FE5 86DC 02E1 B904 8100 0209 C3E0" /* .._.... */ + $"84DC 05E2 A700 1BD3 DE87 DC06 DDDA 2B00" /* ...އ.+. */ + $"008D E59D DC01 E15D 8100 0243 DEDD 9DDC" /* ..]..Cݝ */ + $"06E2 AB03 000E C0E0 85DC 02DF C40C 8100" /* ....... */ + $"0213 CCDE 84DC 05E7 A300 18CE E287 DC06" /* ..ބ.... */ + $"E592 0000 1DD1 DE9D DC01 E15D 8100 0243" /* ...ޝ.]..C */ + $"DDDC 9DDB 07DC DF40 0000 57E4 DC84 DB02" /* ܝ.@..W܄. */ + $"DCE1 5E81 0001 6AE2 84DB 07E1 AF23 0001" /* ^..j.#.. */ + $"32C1 DF85 DB06 DDD5 2700 0077 E49E DB01" /* 2߅.'..w. */ + $"E05D 8100 0243 DDDC 9DDB 08DA E1A9 0400" /* ]..Cܝ... */ + $"05A5 E2DA 83DB 09DA DCD6 712C 2E78 D9DC" /* .ڃq,.x */ + $"DA82 DB02 DCD4 2881 0001 42DE 84DB 07DA" /* ڂ.(..Bބ. */ + $"E471 0000 1DCE DD9E DB01 E05D 8100 0243" /* q...ݞ.]..C */ + $"DDDB 9FDA 07E0 5400 0027 CFDD D983 DA07" /* ۟.T..'ك. */ + $"D9DB E1D8 D9E1 DBD9 83DA 01DE B182 0002" /* ك.ޱ.. */ + $"0FC7 DC82 DA08 D9E1 AD0A 0000 8AE2 D99E" /* .܂...ٞ */ + $"DA01 E05D 8100 0144 DCA0 D907 DCC4 1800" /* .]..Dܠ... */ + $"0052 DEDA 91D9 02DC B905 8100 0216 CCDA" /* .Rڑ.ܹ.... */ + $"81D9 07D8 DDCB 2600 0041 DBA0 D901 DF5D" /* .&..A۠.] */ + $"8100 0144 DBA0 D807 D7E0 9402 0000 72E0" /* ..D۠....r */ + $"92D8 01DA 4C81 0001 68DE 82D8 07DA D642" /* .L..hނ.B */ + $"0000 16BE DCA0 D801 DE5D 8100 0144 DBA2" /* ...ܠ.]..Dۢ */ + $"D707 DE66 0000 027F E0D8 90D7 07D9 CC5E" /* .f....ؐ.^ */ + $"2024 6ED4 D881 D707 DAD8 5100 0005 98DF" /* $n؁.Q... */ + $"A1D7 01DD 5D81 0001 44DA A2D6 08D8 D74A" /* .]..Dڢ.J */ + $"0000 0279 DDD8 90D6 05D8 DDCF D1DD D781" /* ...yؐ.ׁ */ + $"D602 DBD2 4E80 0001 7ADE A2D6 01DC 5D81" /* .N..zޢ.] */ + $"0001 44DA A3D6 02D9 D03F 8000 0260 D4DB" /* ..Dڣ.?..` */ + $"91D6 01D7 D782 D602 DEC1 3A80 0002 6CDC" /* .ׂ.:..l */ + $"D7A2 D601 DC5D 8100 0144 D9A4 D502 D9CE" /* ע.]..D٤. */ + $"4480 0003 38B5 DFD7 94D5 03D9 DC98 1C80" /* D..8ה.ܘ. */ + $"0002 6FDB D6A3 D501 DC5D 8100 0144 D8A5" /* ..o֣.]..Dإ */ + $"D402 D8D2 5980 0004 0F75 CDDD D690 D40A" /* .Y...u֐ */ + $"D7DD BD55 0200 0009 82DB D5A4 D401 DB5D" /* ݽU...Ƃդ.] */ + $"8100 0144 D8A6 D303 D5D8 7F0D 8000 0526" /* ..Dئ.....& */ + $"85CA DCD8 D48A D305 D5DA DBBD 6A13 8000" /* Ԋ.۽j.. */ + $"0323 A2DC D4A5 D301 DA5D 8100 0144 D7A7" /* .#ԥ.]..Dק */ + $"D203 D3DB AE39 8100 141F 68AB D0DB DAD8" /* .ۮ9...h */ + $"D5D4 D4D3 D4D4 D6D8 DBD9 C99C 5411 8000" /* ɜT.. */ + $"0303 57C5 D9A7 D201 D95D 8100 0144 D6A9" /* ..W٧.]..D֩ */ + $"D103 D7D1 8422 8100 0F04 2859 85A5 B9C5" /* .ф"...(Y */ + $"CACB C9C3 B59D 7A4B 1C82 0003 389F D8D4" /* õzK...8 */ + $"A8D1 01D9 5D81 0001 44D6 AAD0 04D1 D9C7" /* .]..D֪. */ + $"7D2A 8400 060B 161E 201C 1408 8300 0506" /* }*..... ...... */ + $"3D95 D1D7 D1A9 D001 D85C 8100 0144 D6AC" /* =ѩ.\..D֬ */ + $"D006 D2D9 CD9B 551F 018A 0006 072A 68AB" /* .͛U.....*h */ + $"D4D7 D1AB D001 D85C 8100 0144 D6AD D016" /* ѫ.\..D֭. */ + $"CFD1 D6D8 C8A5 7A52 3521 150F 0D10 1825" /* ȥzR5!.....% */ + $"3B5C 85AF CED8 D4AE D001 D85C 8100 0144" /* ;\Ԯ.\..D */ + $"D5B1 CF0F D0D4 D7D7 D1C9 C1BC BABD C3CC" /* ձ. */ + $"D3D7 D7D3 B1CF 01D7 5C81 0001 44D4 B6CE" /* ӱ.\..DԶ */ + $"00CF 81D0 01CF CFB5 CE01 D65C 8100 0144" /* .ρ.ϵ.\..D */ + $"D3F5 CD01 D65C 8100 0144 D3F5 CC01 D55C" /* .\..D.\ */ + $"8100 0144 D2F5 CB01 D45C 8100 0144 D1F5" /* ..D.\..D */ + $"CA01 D35C 8100 0144 D1F5 CA01 D35C 8100" /* .\..D.\. */ + $"0144 D1F5 C901 D35C 8100 0144 D0F5 C801" /* .D.\..D. */ + $"D25C 8100 0144 CFF5 C801 D15C 8100 0244" /* \..D.\..D */ + $"C7BA F3BB 02BA C85B 8100 0144 C6F5 BA01" /* Ǻ.[..D. */ + $"C75B 8100 0144 CAF5 C001 CC5C 8100 0144" /* [..D.\..D */ + $"CEF5 C501 CF5C 8100 0144 D1F5 CA01 D35C" /* .\..D.\ */ + $"8100 0144 D5F5 CF01 D75C 8100 0144 D9F5" /* ..D.\..D */ + $"D401 DB5D 8100 0146 DEF5 DA01 E05E 8100" /* .]..F.^. */ + $"022A B5B8 F4B7 01BF 5382 0000 07F5 0801" /* .*.S..... */ + $"0904 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ........ */ + $"FF00 FF00 FF00 FF00 FF00 E700 FF00 FF00" /* ........ */ + $"FF00 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ........ */ + $"FF00 FF00 EE00 0004 A008 0007 D500 0409" /* .......... */ + $"5399 BAC1 A0C0 01A2 48D2 0004 2FB1 E3E2" /* S.H../ */ + $"DFA1 DE03 E2E0 7601 CF00 0230 C9DC A4D5" /* ߡ.v...0ܤ */ + $"03D4 D6DD 60CE 0002 0EB0 D5A7 CF02 D1CB" /* .`...է. */ + $"2BCD 0001 5BD3 A9C9 02D0 9905 CB00 0202" /* +..[ө.Й.... */ + $"99C9 AAC4 01CB 4FCB 0002 0EAE C0AA BE02" /* ɪ.O.... */ + $"C1AE 16CA 0002 13AD BAAB B802 C07F 02C9" /* ....... */ + $"0002 13A8 B5AC B302 B964 03C8 0002 12A2" /* ....d.... */ + $"AFAC AD04 AEB2 7F3B 26BA 2502 2415 0186" /* ..;&%.$.. */ + $"0002 129D AAAE A801 ADAD BCA8 03A0 8240" /* .....@ */ + $"0384 0002 1198 A3EF A204 A3A7 A973 0D83" /* .....s. */ + $"0002 1192 9D81 9CEC 9B04 9C9C A474 0782" /* ....t. */ + $"0007 108D 9897 9594 969A EA9D 059C 9895" /* .... */ + $"959E 4982 0008 0F88 928F 9AB4 CDDD E4E9" /* I... */ + $"E506 E2D5 B99E 9282 0B81 0005 0F82 8DB1" /* .չ.... */ + $"E1F9 EEFF 04F6 D2A6 8D28 8100 030D 7EBD" /* .Ҧ(...~ */ + $"F6F1 FF03 FDDB A137 8100 020A A0F6 F3FF" /* .ۡ7..  */ + $"02FE CD3B 8100 0116 D1F5 FF01 ED46 8100" /* .;....F. */ + $"0136 F1F5 FF01 F852 8100 0143 F7F5 FF01" /* .6.R..C. */ + $"FB5A 8100 0243 F6FF F3FE 02FF FB5F 8100" /* Z..C._. */ + $"0243 F5FF F3FD 02FE FA5F 8100 0243 F5FE" /* .C._..C */ + $"B6FC 82FD B5FC 02FD F95F 8100 0243 F4FD" /* ._..C */ + $"B0FB 0FFC FDFF FFFE FBF7 F5F5 F6F8 FBFE" /* . */ + $"FFFF FDB0 FB02 FCF8 5F81 0002 43F4 FDAE" /* ._..C */ + $"FB14 FDFF F9EB D8C6 B7AE A8A5 A4A5 A9B0" /* .Ʒ */ + $"BBCA DDEF FCFF FDAD FB02 FCF8 5F81 0002" /* ._.. */ + $"43F3 FCAB FA08 FBFE FAE6 C6AC 9D97 9785" /* C.Ƭ */ + $"9809 9797 98A0 B2CF EDFD FDFB AAFA 02FB" /* Ɨ. */ + $"F85F 8100 0243 F3FB A9F9 1DFA FDF7 D8B1" /* _..C.ر */ + $"9B97 9999 9797 9A9E A2A5 A6A5 A19D 9997" /* */ + $"9899 9897 9FBA E3FB FCA9 F902 FAF7 5F81" /* ._ */ + $"0002 43F3 FBA7 F922 F8FB FADA AC98 9899" /* ..C"ڬ */ + $"979B A9BD D1E1 EBF0 F3F4 F2EF E8DD CCB7" /* ̷ */ + $"A499 989A 979A B7E6 FCFA F8A6 F902 FAF7" /* . */ + $"5F81 0002 43F2 FAA7 F802 FBEB B780 991C" /* _..C.뷀. */ + $"98A5 C4E2 F5FB FCFA F9F9 F8F8 F9F9 FAFB" /* */ + $"FCFA F1DB BB9F 979A 979D C6F4 FAA6 F802" /* ۻ. */ + $"F9F6 5F81 0002 43F1 F8A6 F70A FAD6 A197" /* _..C֡ */ + $"9A97 A7D0 F1FB F98B F70B F8FA FAEA C4A0" /* .Ġ */ + $"989A 97AC E5FA A5F7 02F8 F55F 8100 0243" /* ._..C */ + $"F0F7 A4F6 0AF7 F6C5 999A 989E C8F1 FAF7" /* ř */ + $"90F6 09F8 FAE9 BB99 9998 9FD6 F9A4 F602" /* 黙. */ + $"F7F5 5F81 0002 43F0 F6A3 F509 F6F3 BB97" /* _..C */ + $"9A97 AEE4 F9F6 94F5 08F7 F7D7 A398 9A9A" /* .ף */ + $"CEF8 A3F5 02F6 F45F 8100 0243 EFF5 A2F4" /* ._..C */ + $"08F5 F3B8 979A 98BF F2F7 98F4 07F8 E9AF" /* .󸗚. */ + $"989A 99CC F7A2 F402 F5F3 5E81 0002 43EF" /* .^..C */ + $"F5A2 F407 F5BD 979A 99C9 F6F5 82F4 03F7" /* .. */ + $"F4F4 F791 F407 F6F1 B797 9A99 D1F7 A1F4" /* .񷗚 */ + $"02F5 F35E 8100 0243 EEF4 A1F3 07F6 C897" /* .^..C.ȗ */ + $"9A99 CBF6 F481 F305 F4F2 CAAC ADCC 92F3" /* .ʬ̒ */ + $"07F5 F2B8 989A 9CDC F6A0 F302 F4F2 5E81" /* .򸘚.^ */ + $"0002 43EE F3A0 F206 F5DA 9B9A 98C6 F583" /* ..C.ڛ */ + $"F207 F4C1 9699 9996 C6F5 91F2 07F3 F0B3" /* .. */ + $"9899 A5EA F49F F202 F3F2 5E81 0002 43ED" /* .^..C */ + $"F29F F107 F2EB A699 98B9 F3F2 82F1 09F2" /* .릙 */ + $"E8A0 9A9B 9B9A A2EB F291 F106 F3EA A799" /* 蠚.꧙ */ + $"97B8 F39F F102 F2F1 5E81 0002 43EC F19F" /* .^..C */ + $"F006 F3BF 9799 A8EB F283 F002 F2E3 9C80" /* .󿗙.㜀 */ + $"9B03 9A9F E6F1 84F0 04F1 F3F1 F2F3 86F0" /* .. */ + $"06F3 DC9C 9A99 D4F4 9EF0 02F1 F05E 8100" /* .ܜ.^. */ + $"0243 EBF0 9EEF 06F2 DE9C 9A9B D8F3 84EF" /* .C.ޜ */ + $"09F0 EDAA 989B 9B97 AEEF F083 EF07 F0EF" /* . */ + $"CBB0 B2D1 F0F0 85EF 06F3 C498 99A8 ECF0" /* ˰.Ę */ + $"9DEF 02F0 EF5E 8100 0243 EBEF 9EEE 05F0" /* .^..C. */ + $"B598 98BA F186 EE07 F1DC A59A 9AA7 DFF0" /* .ܥ */ + $"84EE 07F0 C096 9898 97CB F185 EE06 EFEB" /* .. */ + $"A899 98CB F29D EE02 EFEF 5E81 0002 43EB" /* .^..C */ + $"EF9D EE07 F0DD 9C9A 9FE2 EFED 85EE 07ED" /* .ݜ. */ + $"F1DA 9AA1 E4F0 ED83 EE08 EFE3 9E9A 9B9B" /* ښ.㞚 */ + $"99A6 EA85 EE06 EDF1 D099 99A7 EA9D EE02" /* .Й. */ + $"EFEF 5E81 0002 43EA EE9D ED05 F0BC 9798" /* ^..C.𼗘 */ + $"BDF0 88ED 05EF DD9A A3E8 EE84 ED01 EFDD" /* .ݚ. */ + $"819B 039A A0E5 EE86 ED05 EBAA 9998 D1F0" /* ..몙 */ + $"9CED 02EE EE5E 8100 0243 EAEE 9CED 06EE" /* .^..C. */ + $"E6A3 9A9C DDEF 88ED 05EF DC9A A3E7 EE85" /* 棚.ܚ */ + $"ED06 E8A6 989B 9B97 AF88 ED05 F0CA 9898" /* .覘.ʘ */ + $"B3EE 9CED 02EE EE5E 8100 0243 E9ED 9CEC" /* .^..C */ + $"05EF D199 99AF ED89 EC05 EEDB 9AA3 E7ED" /* .љ.ۚ */ + $"85EC 07EE D4A1 9A9A A5DC EE87 EC06 EDE3" /* .ԡ. */ + $"A09A 9FE2 ED9B EC02 EDED 5E81 0002 43E9" /* .^..C */ + $"EC9C EB05 EEB9 9898 C9EE 89EB 05ED DA9A" /* ..ښ */ + $"A3E6 EC86 EB05 EFD2 99A2 E1EE 89EB 05ED" /* .ҙ. */ + $"B498 98CE EE9B EB02 ECEC 5E81 0002 43E8" /* .^..C */ + $"EB9C EA05 E8A7 999C DCEC 89EA 05EC D99A" /* .觙.ٚ */ + $"A3E5 EB86 EA05 ECD8 99A5 E7EB 89EA 05ED" /* .ؙ. */ + $"CA98 98BA ED9B EA02 EBEC 5E81 0002 43E7" /* ʘ.^..C */ + $"EA9B E905 EADD 9D99 A7E7 8AE9 05EB D99A" /* .ݝ.ٚ */ + $"A3E7 EB86 E905 EBD6 99A5 E6EA 89E9 05EB" /* .֙. */ + $"DA9C 99AA E89B E902 EAEB 5E81 0002 43E7" /* ڜ.^..C */ + $"E99B E805 EBD1 9998 B4EA 8AE8 07EA D89B" /* .љ.؛ */ + $"9DC8 E5EB E984 E805 EAD5 99A4 E5E9 89E8" /* .ՙ */ + $"06E9 E4A4 99A1 E1E9 9AE8 02E9 EA5E 8100" /* .䤙.^. */ + $"0243 E6E8 9BE7 05EA C698 98C0 EA8A E709" /* .C.Ƙ */ + $"E9D7 9B9A 98A6 C5E1 EAE8 82E7 05E9 D499" /* כ.ԙ */ + $"A4E4 E88A E705 E8AC 999C D9E9 9AE7 02E8" /* .謙. */ + $"E95E 8100 0243 E5E7 9BE6 05E9 BE98 98C8" /* ^..C.龘 */ + $"E98A E60B E8D6 9B9C 9C97 97A2 BFDD E9E8" /* .֛ */ + $"80E6 05E8 D499 A4E3 E78A E605 E9B4 9899" /* .ԙ.鴘 */ + $"D1E9 9AE6 02E7 E95E 8100 0243 E5E7 9BE6" /* .^..C */ + $"05E9 B998 99CE E98A E614 E8D6 9AA2 D2B8" /* .鹘.֚Ҹ */ + $"9E97 979F BAD9 E8E8 E6E8 D499 A4E3 E78A" /* ԙ */ + $"E605 E9BA 9898 CCE9 9AE6 02E7 E95E 8100" /* .麘.^. */ + $"0243 E4E6 9BE5 05E8 B698 99D0 E88A E514" /* .C.趘. */ + $"E7D5 9AA2 E2E9 DABC A197 979D B5D4 E7EA" /* ՚ڼ */ + $"D499 A4E2 E68A E505 E8BD 9898 C9E8 9AE5" /* ԙ.轘 */ + $"02E6 E85E 8100 0243 E4E5 9BE4 05E7 B698" /* .^..C.綘 */ + $"99CF E78A E414 E6D5 9AA2 DFE6 E6E8 DDC1" /* .՚ */ + $"A398 969B AFD1 D299 A4E1 E58A E405 E7BC" /* ҙ. */ + $"9898 C9E7 9AE4 02E5 E75E 8100 0243 E4E4" /* .^..C */ + $"9BE3 05E6 B798 99CC E68A E314 E5D5 9AA2" /* .淘.՚ */ + $"DEE4 E3E3 E5E7 DFC5 A799 979A A49A A4E0" /* ŧ */ + $"E48A E305 E7B9 9899 CAE6 9AE3 02E4 E65E" /* .繘.^ */ + $"8100 0243 E3E4 9BE3 05E6 BC98 98C6 E68A" /* ..C.漘 */ + $"E305 E5D4 9AA2 DEE4 82E3 09E6 E0C9 AB99" /* .Ԛɫ */ + $"989A A4E0 E48A E305 E5B4 9899 CEE5 9AE3" /* .崘 */ + $"02E4 E65E 8100 0243 E2E3 9BE2 05E5 C398" /* .^..C.Ø */ + $"98BD E58A E205 E4D3 9AA2 DDE3 84E2 07E4" /* .Ӛ. */ + $"E2CE AD99 A4DF E38B E204 AC99 9BD4 E39A" /* έ. */ + $"E202 E3E6 5D81 0002 43E2 E29B E105 E3CC" /* .]..C. */ + $"9998 B2E3 8AE1 05E3 D29A A2DC E286 E105" /* .Қ. */ + $"E5D1 99A3 DEE2 8AE1 05DD A399 A0DA E29A" /* љ.ݣ */ + $"E102 E2E5 5D81 0002 43E1 E19B E005 E1D5" /* .]..C. */ + $"9D99 A6DE 8AE0 05E2 D29A A2DB E186 E004" /* ފ.Қ. */ + $"E2CF 99A3 DD8A E005 E1D4 9C99 A8DF 9BE0" /* ϙ݊.Ԝߛ */ + $"02E1 E45D 8100 0243 E0E0 9CDF 05DD A599" /* .]..C.ݥ */ + $"9DD4 E089 DF05 E1D1 9AA2 DBE0 86DF 04E1" /* .њ. */ + $"CE99 A3DC 8ADF 05E2 C498 98B4 E19B DF02" /* Ι܊.Ę. */ + $"E0E3 5D81 0002 43DF DF9C DE05 E0B3 9898" /* ]..Cߜ.೘ */ + $"C2E1 89DE 05E1 CE9A A0D7 E086 DE04 E0CE" /* .Κ. */ + $"99A3 DB8A DE05 E0B1 9999 C5E1 9BDE 02DF" /* ۊ.ౙ. */ + $"E35D 8100 0243 DFDF 9CDE 05E1 C799 99AD" /* ]..Cߜ.Ǚ */ + $"DF88 DE07 E0D0 A39A 9AA5 D2DF 85DE 04E0" /* ߈.У߅. */ + $"CE99 A3DB 89DE 06DF D8A0 9A9E D5DF 9BDE" /* Ιۉ.ؠߛ */ + $"02DF E35D 8100 0243 DFDE 9CDD 06DE D7A0" /* .]..Cޜ.נ */ + $"9A9C D2DF 86DD 07DE DCA7 989B 9B98 AA86" /* ߆.ܧ */ + $"DD04 DFCD 99A3 DA89 DD05 E0C2 9899 ADDE" /* .͙ډ.˜ */ + $"9DDD 01E2 5D81 0002 43DE DD9D DC05 DFB4" /* .]..Cݝ.ߴ */ + $"9898 B7DF 86DC 02DD D29C 809B 039A 9ED5" /* ߆.Ҝ. */ + $"DD84 DC04 DECC 99A3 DA88 DC06 DDDB A899" /* ݄.̙ڈ.ۨ */ + $"99C5 DF9D DC01 E15D 8100 0243 DDDC 9DDB" /* ߝ.]..Cܝ */ + $"06DD CD9B 9A9F D3DD 85DB 09DC D49E 9A9B" /* .͛݅Ԟ */ + $"9B9A A1D7 DC84 DB05 DFCA 99A2 D7DD 87DB" /* ܄.ʙ݇ */ + $"06DE C699 9AA3 D8DC 9DDB 01E0 5D81 0002" /* .ƙܝ.].. */ + $"43DD DB9E DA05 DCAE 9998 B4DD 86DA 07DC" /* C۞.ܮ݆. */ + $"B697 9999 97BA DC84 DA07 DCCE A49A 9BA9" /* ܄.Τ */ + $"D3DC 85DA 06DB D8A6 9A98 BEDD 9EDA 01E0" /* ܅.ئݞ. */ + $"5D81 0002 43DD DB9E DA07 DBCB 9B9A 9BCA" /* ]..C۞.˛ */ + $"DCD9 85DA 05D8 BBA7 A7BD D985 DA07 D8A6" /* م.ػم.ئ */ + $"989B 9B98 AEDB 84DA 06D9 DCBB 989A A3D6" /* ۄ.ܻ */ + $"9FDA 01E0 5D81 0001 44DB A0D9 07DA B398" /* .]..D۠.ڳ */ + $"99A6 D6D9 D883 D907 D8D9 DBD8 D8DB D9D8" /* ؃. */ + $"83D9 01DA CD81 9B02 9A9F D383 D908 D8DB" /* .́.Ӄ. */ + $"CC9D 9A99 C2DB D89E D901 DF5D 8100 0144" /* ̝؞.]..D */ + $"DBA0 D806 D9D2 A29A 98B2 D992 D802 D9CF" /* ۠.Ңْ. */ + $"9C80 9B02 9AA1 D483 D805 D9D4 A599 99AD" /* .ԃ.ԥ */ + $"A1D8 01DE 5D81 0001 44DB A1D7 06D9 C49A" /* .]..Dۡ.Ě */ + $"9B99 BBD9 92D7 07D8 B097 9A9A 97B8 D982" /* ْ.ذق */ + $"D707 D8D7 AD99 9AA1 D0D8 A0D7 01DD 5D81" /* .׭ؠ.] */ + $"0001 44DB A2D7 06D9 B799 9B9A BED9 91D7" /* ..Dۢ.ٷّ */ + $"06D8 D4B5 A3A4 B9D6 82D7 07D8 D7B1 989A" /* .Եւ.ױ */ + $"9BC5 D9A1 D701 DD5D 8100 0144 DAA4 D605" /* ١.]..Dڤ. */ + $"AF98 9A9B BCD8 92D6 03D8 D4D4 D882 D607" /* ؒ.؂. */ + $"D7D5 B099 9B99 BCD8 A2D6 01DC 5D81 0001" /* հآ.].. */ + $"44D9 A3D5 08D6 D3AC 999A 9AB5 D4D6 98D5" /* D٣.Ӭ֘ */ + $"07D7 CFAA 999B 99B8 D7A3 D501 DC5D 8100" /* .Ϫף.]. */ + $"0144 D8A4 D409 D5D2 AD99 9B99 AACB D6D5" /* .Dؤҭ */ + $"94D4 08D5 D6C4 A299 9A9A B9D5 A4D4 01DB" /* .Ģդ. */ + $"5D81 0001 44D8 A5D3 0AD4 D2B2 999A 999E" /* ]..DإҲ */ + $"BAD1 D5D4 90D3 09D4 D5CD B19B 9A9A 9CBD" /* Ԑͱ */ + $"D5A5 D301 DA5D 8100 0144 D7A6 D20B D3D4" /* ե.]..Dצ. */ + $"BC9E 999A 99A4 BED0 D5D3 8BD2 0BD3 D4D4" /* Ӌ. */ + $"CCB6 9F99 9A99 A3C5 D4A6 D201 D95D 8100" /* ̶Ԧ.]. */ + $"0144 D6A8 D10A D3C8 A999 9A9A 99A3 B6C7" /* .D֨ȩ */ + $"D180 D384 D280 D30A CFC3 B09F 999A 999B" /* рӄҀð */ + $"B1CE D3A7 D101 D95D 8100 0144 D6A9 D020" /* ӧ.]..D֩ */ + $"D2D0 BCA3 999A 9A99 9CA5 B1BD C5CA CDCE" /* м */ + $"CFCE CDC9 C3BA AEA2 9A99 9A99 9AA9 C3D2" /* ú */ + $"D1A8 D001 D85C 8100 0144 D6AB D01C D2CE" /* Ѩ.\..D֫. */ + $"BBA5 9A99 9A9A 9999 9B9E A1A2 A3A2 A09D" /* */ + $"9A99 999A 9A99 9CAA C1D0 D2AA D001 D85C" /* Ҫ.\ */ + $"8100 0144 D5AC CF06 D0D2 CFC2 B0A2 9B80" /* ..Dլ.° */ + $"9984 9A80 9906 9CA5 B5C6 D0D1 D0AB CF01" /* .Ы. */ + $"D75C 8100 0144 D5AF CF14 D0D1 CDC4 B9AF" /* \..Dկ.Ĺ */ + $"A8A3 A09F 9E9F A1A4 A9B1 BCC7 CED1 D0AE" /* Ю */ + $"CF01 D65C 8100 0144 D4B2 CE05 CFD0 D0CE" /* .\..DԲ. */ + $"CCCA 80C9 05CB CDCF D0D0 CFB1 CE01 D65C" /* ʀ.ϱ.\ */ + $"8100 0144 D3F5 CD01 D65C 8100 0144 D3F5" /* ..D.\..D */ + $"CC01 D55C 8100 0144 D2F5 CB01 D45C 8100" /* .\..D.\. */ + $"0144 D1F5 CA01 D35C 8100 0144 D1F5 C901" /* .D.\..D. */ + $"D35C 8100 0144 D1F5 C901 D35C 8100 0144" /* \..D.\..D */ + $"D0F5 C801 D25C 8100 0144 CFF5 C701 D15C" /* .\..D.\ */ + $"8100 0144 CFF5 C701 D05C 8100 0244 C6B8" /* ..D.\..DƸ */ + $"F3B9 02B8 C75B 8100 0244 C5B8 F3B9 02B8" /* .[..DŸ. */ + $"C65B 8100 0144 C9F5 BF01 CB5C 8100 0144" /* [..D.\..D */ + $"CDF5 C401 CF5C 8100 0144 D1F5 C901 D35C" /* .\..D.\ */ + $"8100 0144 D4F5 CE01 D65C 8100 0144 D8F5" /* ..D.\..D */ + $"D301 DA5D 8100 0146 DEF5 D901 E05E 8100" /* .]..F.^. */ + $"022A B5B7 F4B6 01BE 5382 0000 07F5 0801" /* .*.S..... */ + $"0904 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ........ */ + $"FF00 FF00 FF00 FF00 FF00 E700 FF00 FF00" /* ........ */ + $"FF00 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ........ */ + $"FF00 FF00 EE00 0004 A008 0007 D500 0409" /* .......... */ + $"549A BAC1 9EC0 03C1 C0A3 48D2 0004 2FB2" /* T.H../ */ + $"E3E3 DFA1 DE03 E2E1 7601 CF00 0230 C9DC" /* ߡ.v...0 */ + $"A5D5 02D6 DD60 CE00 020E B0D6 A7CF 02D1" /* .`...֧. */ + $"CB2B CD00 025B D3C9 A7CA 03C9 D09A 05CB" /* +..[ɧ.К. */ + $"0002 0299 CAAA C401 CB4F CB00 020E AEC1" /* ...ʪ.O... */ + $"AABE 02C1 AE16 CA00 0213 ADBB ABB9 02C0" /* ...... */ + $"7F02 C900 0213 A8B5 ACB3 02B9 6503 C800" /* ......e.. */ + $"0212 A3B0 ADAE 03B3 803B 26BA 2502 2415" /* ...;&%.$. */ + $"0186 0002 129E AAAE A802 ADAD A9BB A803" /* ...... */ + $"A083 4003 8400 0211 98A4 AFA2 BDA3 04A4" /* @..... */ + $"A7A9 730E 8300 0211 939E 819D ED9C 039D" /* s..... */ + $"A575 0782 0007 108E 9897 9694 969B EA9E" /* u.... */ + $"059D 9995 959F 4A82 0008 0F89 928F 9AB4" /* .J... */ + $"CDDD E4E9 E506 E2D5 B99E 9283 0B81 0005" /* .չ... */ + $"0F82 8EB1 E1F9 EEFF 04F6 D2A7 8E28 8100" /* ..ҧ(. */ + $"030D 7FBD F6F1 FF03 FDDB A237 8100 020A" /* ....ۢ7.. */ + $"A0F6 F3FF 02FE CD3B 8100 0116 D1F5 FF01" /* .;.... */ + $"ED46 8100 0136 F1F5 FF01 F852 8100 0143" /* F..6.R..C */ + $"F7F5 FF01 FB5A 8100 0243 F6FF F3FE 02FF" /* .Z..C. */ + $"FB5F 8100 0243 F5FF F3FD 02FE FA5F 8100" /* _..C._. */ + $"0243 F5FE B6FC 81FD B6FC 02FD F95F 8100" /* .C._. */ + $"0243 F5FE B1FC 05FD FEFF FEFC FA80 F805" /* .C.. */ + $"FAFC FEFF FEFD B0FC 02FD F95F 8100 0243" /* ._..C */ + $"F4FD ADFB 15FC FDFE FAF1 E5D9 D0CA C6C4" /* . */ + $"C4C5 C7CB D2DC E8F4 FCFE FCAD FB02 FCF8" /* . */ + $"5F81 0002 43F3 FCAB FA08 FBFD FAED DAC9" /* _..C. */ + $"BFBC BB86 BC07 BBBC C1CD DFF2 FCFC ABFA" /* . */ + $"02FB F85F 8100 0243 F3FB A9F9 1DFA FCF8" /* ._..C. */ + $"E5CC BEBB BCBD BCBC BDC0 C2C4 C5C4 C2BF" /* ̾¿ */ + $"BDBC BCBD BCBB C1D2 EBFA FBA9 F902 FAF7" /* . */ + $"5F81 0002 43F3 FBA7 F90F F8FA F9E5 C9BC" /* _..C.ɼ */ + $"BCBD BCBE C7D3 E0EA F0F3 80F5 0FF3 EEE7" /* . */ + $"DDD0 C4BD BCBD BCBE D0ED FBF9 F8A6 F902" /* Ľ. */ + $"FAF7 5F81 0002 43F2 FAA7 F80E FAF0 D0BC" /* _..C.м */ + $"BCBD BCC4 D7EA F6FA FAF9 F982 F80E F9FA" /* . */ + $"FAF9 F4E6 D2C1 BCBD BCBF D9F5 F9A6 F802" /* . */ + $"F9F6 5F81 0002 43F1 F8A6 F70A F8E3 C2BC" /* _..C¼ */ + $"BDBC C5DE F3F9 F88C F70A F9F9 EFD7 C1BC" /* */ + $"BDBB C9EC F9A5 F702 F8F5 5F81 0002 43F0" /* ._..C */ + $"F7A6 F607 D8BD BDBC C0DA F3F8 91F6 09F7" /* .ؽ */ + $"F8EE D1BD BDBC C1E3 F8A4 F602 F7F5 5F81" /* ѽ._ */ + $"0002 43F0 F6A3 F509 F6F4 D2BC BEBC CAEB" /* ..CҼ */ + $"F7F6 94F5 08F6 F7E3 C3BC BDBE DDF7 A3F5" /* .ü */ + $"02F6 F45F 8100 0243 F0F6 A3F5 07F4 D0BC" /* ._..C.м */ + $"BEBC D4F3 F698 F507 F7EE CABC BDBD DCF7" /* .ʼ */ + $"A2F5 02F6 F45F 8100 0243 EFF5 A2F4 07F5" /* ._..C. */ + $"D2BC BEBD DAF5 F582 F403 F6F4 F4F6 91F4" /* Ҽ. */ + $"07F5 F2CF BCBE BDDF F6A1 F402 F5F3 5E81" /* .ϼ.^ */ + $"0002 43EE F4A1 F306 F5D9 BCBE BDDB F583" /* ..C.ټ */ + $"F304 F2DA C9C9 DC92 F307 F4F2 CFBC BDBF" /* .ܒ.ϼ */ + $"E5F5 A0F3 02F4 F25E 8100 0243 EEF3 A0F2" /* .^..C */ + $"06F4 E4BE BEBC D8F4 83F2 07F3 D5BB BDBC" /* .侾.ջ */ + $"BBD8 F491 F207 F3F1 CCBC BDC4 EDF3 9FF2" /* .̼ */ + $"02F3 F25E 8100 0243 EDF2 9FF1 07F2 EDC5" /* .^..C. */ + $"BDBC D0F2 F282 F109 F2EC C1BD BEBE BDC2" /* */ + $"EEF2 91F1 06F2 EDC5 BDBC CFF2 9FF1 02F2" /* .Ž. */ + $"F15E 8100 0243 ECF1 9FF0 06F2 D3BC BDC6" /* ^..C.Ӽ */ + $"EDF1 83F0 02F1 E8BF 81BE 02C0 EAF1 85F0" /* .迁. */ + $"03F2 F1F1 F286 F006 F2E4 BFBE BDDF F29E" /* ..俾 */ + $"F002 F1F0 5E81 0002 43EB F09E EF06 F1E5" /* .^..C. */ + $"BFBE BEE2 F184 EF07 F0EE C7BC BEBE BCC9" /* .Ǽ */ + $"85EF 06F0 EFDA CACB DEF0 86EF 06F2 D6BC" /* ..ּ */ + $"BDC6 EDF0 9DEF 02F0 EF5E 8100 0243 EBEF" /* .^..C */ + $"9EEE 05F0 CDBC BCD0 F086 EE07 F0E4 C4BE" /* .ͼ.ľ */ + $"BDC5 E5EF 84EE 02F0 D3BB 80BC 01DA F085" /* .ӻ. */ + $"EE06 EFEC C5BD BCDA F09D EE02 EFEF 5E81" /* .Ž.^ */ + $"0002 43EB EF9D EE06 EFE4 BEBD C0E7 EF87" /* ..C.侽 */ + $"EE05 F0E2 BDC2 E9EF 84EE 02EF E8C0 80BE" /* .. */ + $"02BD C4EC 86EE 05F0 DDBC BDC5 EC9D EE02" /* ..ݼ. */ + $"EFEF 5E81 0002 43EB EF9D EE05 EFD1 BCBC" /* ^..C.Ѽ */ + $"D1F0 88EE 04EF E4BD C3EB 85EE 01EF E482" /* .. */ + $"BE01 C1E9 87EE 05ED C6BD BCDD EF9C EE02" /* ..ƽ. */ + $"EFEF 5E81 0002 43EA EE9D ED05 E9C2 BDBF" /* ^..C.½ */ + $"E4EE 88ED 04EE E3BD C3EA 86ED 06EA C4BD" /* ..Ľ */ + $"BEBE BCC9 88ED 05EF D9BC BCCC EE9C ED02" /* Ɉ.ټ. */ + $"EEEE 5E81 0002 43E9 ED9C EC04 EEDC BDBD" /* ^..C.ܽ */ + $"C98A EC04 EDE2 BDC2 E986 EC07 EDDE C1BE" /* Ɋ.. */ + $"BEC4 E3ED 88EC 04E7 C1BD C1E6 9CEC 02ED" /* .. */ + $"ED5E 8100 0243 E9EC 9CEB 05ED CFBC BCD8" /* ^..C.ϼ */ + $"ED89 EB04 ECE2 BDC2 E887 EB05 EDDD BDC2" /* ..ݽ */ + $"E6EC 89EB 05EC CCBC BCDB ED9B EB02 ECEC" /* .̼. */ + $"5E81 0002 43E8 EB9C EA05 E9C5 BDBF E2EB" /* ^..C.Ž */ + $"89EA 04EB E1BD C2E7 87EA 04EB DFBD C3E8" /* ..߽ */ + $"8AEA 05EC D8BC BCCF EC9B EA02 EBEC 5E81" /* .ؼ.^ */ + $"0002 43E7 EA9B E905 EAE3 BFBD C5E8 8AE9" /* ..C.㿽 */ + $"04EA E0BD C2E7 87E9 04EA DEBD C3E7 8AE9" /* ..޽ */ + $"04EA E1BF BDC6 9CE9 02EA EB5E 8100 0243" /* .´Ɯ.^..C */ + $"E7E9 9BE8 05E9 DCBD BCCC E98A E807 E9DF" /* .ܽ. */ + $"BEBF D7E6 EAE9 84E8 04E9 DEBD C3E6 8BE8" /* .޽ */ + $"05E6 C3BD C1E4 E99A E802 E9EA 5E81 0002" /* .ý.^.. */ + $"43E6 E89B E705 E9D5 BCBC D2E9 8AE7 09E8" /* C.ռ */ + $"DEBE BEBD C4D5 E4E9 E882 E704 E8DD BDC3" /* ޾.ݽ */ + $"E58C E704 C8BD BEDF E89A E702 E8E9 5E81" /* .Ƚ.^ */ + $"0002 43E6 E89B E705 E9D1 BCBC D7E9 8AE7" /* ..C.Ѽ */ + $"0BE8 DEBE BFBE BCBC C2D2 E2E9 E880 E704" /* .޾. */ + $"E8DD BDC3 E58B E705 E8CC BCBD DCE8 9AE7" /* ݽ.̼ */ + $"02E8 E95E 8100 0243 E5E7 9BE6 05E8 CEBC" /* .^..C.μ */ + $"BDD9 E88A E613 E7DE BDC2 DBCD C0BC BCC0" /* .޽ */ + $"CFDF E7E7 E6E7 DCBD C3E4 8BE6 05E8 CFBC" /* ܽ.ϼ */ + $"BDD9 E89A E602 E7E9 5E81 0002 43E4 E69B" /* .^..C */ + $"E505 E6CC BCBD DAE6 8AE5 13E6 DDBD C2E3" /* .̼.ݽ */ + $"E7DF CFC1 BCBC BFCC DCE6 E8DC BDC3 E38B" /* ܽ */ + $"E505 E7D0 BCBD D6E7 9AE5 02E6 E85E 8100" /* .м.^. */ + $"0243 E4E5 9BE4 05E5 CCBC BDD9 E58A E413" /* .C.̼. */ + $"E5DC BDC2 E1E4 E5E6 E1D2 C3BC BCBE C9DB" /* ܽü */ + $"DABD C3E2 8BE4 05E7 D0BC BDD6 E69A E402" /* ڽ.м. */ + $"E5E7 5E81 0002 43E4 E49B E305 E5CD BCBD" /* ^..C.ͼ */ + $"D7E4 8AE3 04E4 DBBD C2E1 80E3 0BE4 E5E1" /* .۽. */ + $"D4C4 BDBC BEC3 BDC3 E18B E305 E5CE BCBD" /* Ľý.μ */ + $"D7E4 9AE3 02E4 E65E 8100 0243 E3E4 9BE3" /* .^..C */ + $"05E5 CFBC BDD4 E48A E304 E4DB BDC2 E183" /* .ϼ.۽ */ + $"E308 E4E2 D6C6 BDBC BDC3 E18B E305 E4CB" /* .ƽ. */ + $"BDBD D8E4 9AE3 02E4 E65E 8100 0243 E2E3" /* .^..C */ + $"9BE2 05E3 D2BC BCCF E48A E204 E3DA BDC1" /* .Ҽ.ڽ */ + $"E085 E206 E3E2 D8C7 BDC2 E08C E204 C6BD" /* .ǽ.ƽ */ + $"BEDB E39A E202 E3E6 5D81 0002 43E2 E29B" /* .]..C */ + $"E105 E2D6 BDBD CAE2 8AE1 04E2 DABE C1DF" /* .ֽ.ھ */ + $"87E1 04E3 D9BD C2DF 8BE1 04DF C2BD C0DD" /* .ٽߋ.½ */ + $"9BE1 02E2 E55D 8100 0243 E1E1 9BE0 05E1" /* .]..C. */ + $"DBBF BDC4 DF8A E004 E1D9 BEC1 DE87 E004" /* ۿߊ.پއ. */ + $"E1D8 BDC2 DE8A E004 E1DA BEBD C49C E002" /* ؽފ.ھĜ. */ + $"E1E4 5D81 0002 43E1 E19C E005 DFC3 BDBF" /* ]..C.ý */ + $"DAE1 89E0 04E1 D9BE C1DE 87E0 04E1 D8BD" /* .پއ.ؽ */ + $"C2DE 8AE0 05E1 D3BD BDCB E19B E002 E1E4" /* ފ.ӽ. */ + $"5D81 0002 43E0 E09C DF05 E0CA BDBD D1E0" /* ]..C.ʽ */ + $"89DF 05E1 D7BE C0DC E086 DF04 E0D7 BDC2" /* .׾.׽ */ + $"DE8A DF05 E0C9 BDBD D3E0 9BDF 02E0 E35D" /* ފ.ɽ.] */ + $"8100 0243 DFDF 9CDE 05DF D3BD BDC6 DF88" /* ..Cߜ.ӽ߈ */ + $"DE07 DFD7 C2BE BEC3 D8DF 85DE 04DF D6BD" /* .¾߅.ֽ */ + $"C2DD 8ADE 05DB C0BD BFDA DF9B DE02 DFE3" /* ݊.ߛ. */ + $"5D81 0002 43DF DE9D DD05 DAC0 BDBF D8DE" /* ]..Cޝ. */ + $"88DD 05C4 BDBE BEBD C586 DD04 DED5 BDC2" /* .Ľņ.ս */ + $"DC89 DD05 DED1 BDBD C6DE 9DDD 01E2 5D81" /* ܉.ѽޝ.] */ + $"0002 43DE DD9D DC05 DDC9 BDBD CBDD 86DC" /* ..Cݝ.ɽ݆ */ + $"02DD D7BF 81BE 01BF D985 DC04 DDD5 BDC2" /* .׿.م.ս */ + $"DB8A DC04 C4BD BDD1 DD9D DC01 E15D 8100" /* ۊ.Ľݝ.]. */ + $"0243 DDDC 9DDB 06DC D5BE BEC0 D8DC 85DB" /* .Cܝ.վ܅ */ + $"02DC D8C0 81BE 01C0 D985 DB05 DDD4 BDC1" /* ..م.Խ */ + $"DADC 87DB 05DC D1BD BEC2 DA9E DB01 E05D" /* ܇.ѽڞ.] */ + $"8100 0243 DDDB 9EDA 05DB C6BD BDC9 DB86" /* ..C۞.ƽۆ */ + $"DA07 DBCA BCBD BDBC CCDB 84DA 07DB D5C2" /* .ʼۄ. */ + $"BEBE C4D7 DB85 DA06 DBDA C3BD BDCD DC9E" /* ۅ.ýܞ */ + $"DA01 E05D 8100 0243 DDDA 9ED9 01DA D380" /* .]..Cڞ.Ӏ */ + $"BE01 D3DA 85D9 07DA D9CC C3C3 CDD9 DA85" /* .څ.څ */ + $"D906 C3BD BEBE BCC6 DA85 D906 DBCC BDBD" /* .ýڅ.̽ */ + $"C2D8 DA9E D901 E05D 8100 0244 DCDA 9FD9" /* ڞ.]..Dڟ */ + $"05DA C8BD BDC3 D887 D903 DAD9 D9DA 85D9" /* .Ƚ؇.څ */ + $"01DA D482 BE01 C0D7 84D9 06DA D3BF BEBD" /* .Ԃ.ׄ.ӿ */ + $"CFDA 9FD9 01DF 5D81 0001 44DC A1D9 04D6" /* ڟ.]..Dܡ. */ + $"C1BE BDC8 94D9 01D5 BF81 BE01 C1D7 84D9" /* Ȕ.տ.ׄ */ + $"04D7 C3BD BDC6 A1D9 01DF 5D81 0001 44DB" /* .ýơ.]..D */ + $"A1D8 06D9 D0BE BEBD CCD9 93D8 06C7 BCBE" /* .оٓ.Ǽ */ + $"BDBC CAD9 83D8 05D7 C6BD BEC0 D5A1 D801" /* ك.ƽա. */ + $"DE5D 8100 0144 DBA2 D706 D8CA BDBE BECD" /* ]..Dۢ.ʽ */ + $"D892 D704 D6C9 C1C2 CB85 D705 C7BD BEBE" /* ؒ.˅.ǽ */ + $"D0D8 A1D7 01DD 5D81 0001 44DA A4D6 05C6" /* ء.]..Dڤ. */ + $"BDBE BECC D792 D603 D7D5 D5D7 84D6 05C7" /* ג.ׄ. */ + $"BDBE BDCC D7A2 D601 DC5D 8100 0144 D9A4" /* ע.]..D٤ */ + $"D505 D4C5 BDBE BDC8 9AD5 07D6 D3C4 BDBE" /* .ŽȚ.Ľ */ + $"BDCA D6A3 D501 DC5D 8100 0144 D8A5 D407" /* ֣.]..Dإ. */ + $"D3C5 BDBE BDC4 D1D5 96D4 07D5 CEC1 BDBE" /* ŽՖ. */ + $"BDC9 D5A4 D401 DB5D 8100 0144 D8A7 D307" /* դ.]..Dا. */ + $"C7BD BEBD BFCA D2D4 92D3 08D4 D1C6 BEBE" /* ǽԒ.ƾ */ + $"BDBE CBD4 A5D3 01DA 5D81 0001 44D7 A7D2" /* ԥ.]..Dק */ + $"0AD3 CABF BDBE BDC1 CBD1 D3D3 8CD2 0AD3" /* ʿӌ */ + $"D3D0 C8C0 BDBE BDC1 CDD3 A6D2 01D9 5D81" /* Ӧ.] */ + $"0001 44D7 A8D2 0CD3 CEC3 BDBE BEBD C1C8" /* ..Dר.ý */ + $"CED2 D3D3 85D2 80D3 0AD1 CDC6 BFBD BEBD" /* ӅҀƿ */ + $"BEC6 D1D3 A7D2 01D9 5D81 0001 44D6 A9D1" /* ӧ.]..D֩ */ + $"0ED2 D1CA C1BD BEBE BDBE C2C6 CACD CFD0" /* . */ + $"80D1 0ED0 CFCD C9C5 C1BE BDBE BDBE C3CD" /* . */ + $"D2D2 A8D1 01D9 5D81 0001 44D6 AAD0 07D1" /* Ҩ.]..D֪. */ + $"D1D0 C9C2 BEBD BE80 BD02 BEBF C080 C10C" /* ¾.. */ + $"C0BF BEBD BDBE BEBD BEC3 CBD1 D1AA D001" /* Ѫ. */ + $"D85C 8100 0144 D5AC CF06 D0D0 CFCB C5C0" /* \..Dլ. */ + $"BE80 BD80 BE00 BD80 BE80 BD05 BFC1 C7CC" /* .. */ + $"D0D0 ACCF 01D7 5C81 0001 44D5 B1CF 06CE" /* Ь.\..Dձ. */ + $"CBC7 C4C2 C1C0 80BF 06C0 C1C3 C5C8 CCCE" /* . */ + $"B0CF 01D6 5C81 0001 44D4 B6CE 01CD CD80" /* .\..DԶ.̀ */ + $"CC01 CDCD B5CE 01D6 5C81 0001 44D3 F5CD" /* .͵.\..D */ + $"01D6 5C81 0001 44D3 F5CC 01D5 5C81 0001" /* .\..D.\.. */ + $"44D2 F5CB 01D4 5C81 0001 44D2 F5CB 01D4" /* D.\..D. */ + $"5C81 0001 44D1 F5CA 01D3 5C81 0001 44D1" /* \..D.\..D */ + $"F5C9 01D3 5C81 0001 44D0 F5C8 01D2 5C81" /* .\..D.\ */ + $"0001 44CF F5C7 01D1 5C81 0001 44CF F5C7" /* ..D.\..D */ + $"01D0 5C81 0002 44C6 B9F3 BA02 B9C7 5B81" /* .\..Dƹ.[ */ + $"0001 44C5 F5B9 01C7 5B81 0001 44C9 F5BF" /* ..D.[..D */ + $"01CB 5C81 0001 44CD F5C4 01CF 5C81 0001" /* .\..D.\.. */ + $"44D1 F5C9 01D3 5C81 0001 44D4 F5CE 01D7" /* D.\..D. */ + $"5C81 0001 44D8 F5D4 01DB 5D81 0002 46DE" /* \..D.]..F */ + $"DAF4 D901 E05E 8100 012B B5F5 B701 BF53" /* .^..+.S */ + $"8200 0007 F508 0109 04FF 00FF 00FF 00FF" /* ......... */ + $"00FF 00FF 00FF 00FF 00FF 00FF 00FF 00FF" /* ........ */ + $"00E7 0074 386D 6B00 0040 0800 0000 0000" /* ..t8mk..@...... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0508 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0700 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"000B 5FAD D3DB DBDB DBDB DBDB DBDB DBDB" /* .._ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB D9B7 5300 0000 0000" /* ٷS..... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"37CC FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* 7 */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FF8A 0100 0000" /* .... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 003A" /* ...............: */ + $"EDFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF 7400 0000" /* t... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 11D8" /* ............... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF F636 0000" /* 6.. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 73FF" /* ..............s */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFC3 0600" /* .. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0003 C7FF" /* .............. */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF 6800" /* h. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0013 E9FF" /* .............. */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF E71D" /* . */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 001B EFFF" /* .............. */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFAF" /* */ + $"0300 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 001B EFFF" /* .............. */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"8F04 0000 0000 0000 0000 0000 0000 0000" /* ............... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 001B EFFF" /* .............. */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFBB 5738 3737 3737 3737 3737 3737 3737" /* W8777777777777 */ + $"3737 3737 3737 3737 3737 3737 3737 3737" /* 7777777777777777 */ + $"3737 3737 3737 3737 3737 3737 3737 3737" /* 7777777777777777 */ + $"3737 3737 3737 3737 3737 3737 3737 3737" /* 7777777777777777 */ + $"3734 1F01 0000 0000 0000 0000 001B EFFF" /* 74............ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFE F2C4 6104 0000 0000 0000 001B EFFF" /* a......... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFB3 1500 0000 0000 001B EFFF" /* ........ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF BE0B 0000 0000 001B EFFF" /* ....... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FF7D 0000 0000 001B EFFF" /* }...... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFE5 1300 0000 001B EFFF" /* ...... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 4900 0000 001B EFFF" /* I..... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 6D00 0000 001A EFFF" /* m..... */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7400 0000 0021 F4FF" /* t....! */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0046 FFFF" /* s....F */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0056 FFFF" /* s....V */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* s....U */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7300 0000 0058 FFFF" /* s....X */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* */ + $"FFFF FFFF FFFF FFFF 7500 0000 0034 D6DB" /* u....4 */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* */ + $"DBDB DBDB DBDB DBDB 6300 0000 0000 0708" /* c....... */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0809 0400 0000 0000 0000" /* ............... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 00" /* ........... */ +}; + diff --git a/cmake/installer/installer-header.bmp b/cmake/installer/installer-header.bmp new file mode 100644 index 0000000000..7588a338b1 Binary files /dev/null and b/cmake/installer/installer-header.bmp differ diff --git a/cmake/installer/installer.ico b/cmake/installer/installer.ico new file mode 100644 index 0000000000..f6ec3c7108 Binary files /dev/null and b/cmake/installer/installer.ico differ diff --git a/cmake/installer/uninstaller-header.bmp b/cmake/installer/uninstaller-header.bmp new file mode 100644 index 0000000000..d43166ded3 Binary files /dev/null and b/cmake/installer/uninstaller-header.bmp differ diff --git a/cmake/macros/ConsolidateStackComponents.cmake b/cmake/macros/ConsolidateStackComponents.cmake deleted file mode 100644 index ca272f6485..0000000000 --- a/cmake/macros/ConsolidateStackComponents.cmake +++ /dev/null @@ -1,28 +0,0 @@ -macro(CONSOLIDATE_STACK_COMPONENTS) - - if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE) - if (WIN32) - # Copy all the output for this target into the common deployment location - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_directory $ ${CMAKE_BINARY_DIR}/full-stack-deployment - ) - - # Copy icon files for interface and stack manager - if (TARGET_NAME STREQUAL "interface" OR TARGET_NAME STREQUAL "stack-manager") - if (TARGET_NAME STREQUAL "interface") - set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/icon/interface.ico") - set (ICON_DESTINATION_NAME "interface.ico") - elseif (TARGET_NAME STREQUAL "stack-manager") - set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/assets/icon.ico") - set (ICON_DESTINATION_NAME "stack-manager.ico") - endif () - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/full-stack-deployment/${ICON_DESTINATION_NAME} - ) - endif () - endif () - endif () - -endmacro() \ No newline at end of file diff --git a/cmake/macros/FixPathForNSIS.cmake b/cmake/macros/FixPathForNSIS.cmake new file mode 100644 index 0000000000..22ee538ed7 --- /dev/null +++ b/cmake/macros/FixPathForNSIS.cmake @@ -0,0 +1,16 @@ +# +# FixPathForNSIS.cmake +# +# Created by Sam Gateau on 1/14/16. +# Copyright 2016 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(fix_path_for_nsis INITIAL_PATH RESULTING_PATH) + # replace forward slash with backslash + string(REPLACE "/" "\\\\" _BACKSLASHED_PATH ${INITIAL_PATH}) + # set the resulting path variable + set(${RESULTING_PATH} ${_BACKSLASHED_PATH}) +endmacro() diff --git a/cmake/macros/FixupInterface.cmake b/cmake/macros/FixupInterface.cmake new file mode 100644 index 0000000000..3e5ea7a3e2 --- /dev/null +++ b/cmake/macros/FixupInterface.cmake @@ -0,0 +1,58 @@ +# +# FixupInterface.cmake +# cmake/macros +# +# Copyright 2016 High Fidelity, Inc. +# Created by Stephen Birarda on January 6th, 2016 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(fixup_interface) + if (APPLE) + + string(REPLACE " " "\\ " ESCAPED_BUNDLE_NAME ${INTERFACE_BUNDLE_NAME}) + string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${INTERFACE_INSTALL_DIR}) + set(_INTERFACE_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app") + + # install QtWebProcess from Qt to the application bundle + # since it is missed by macdeployqt + # https://bugreports.qt.io/browse/QTBUG-35211 + set(LIBEXEC_PATH "${_INTERFACE_INSTALL_PATH}/Contents/libexec") + install( + PROGRAMS "${QT_DIR}/libexec/QtWebProcess" + DESTINATION ${LIBEXEC_PATH} + COMPONENT ${CLIENT_COMPONENT} + ) + + set(QTWEBPROCESS_PATH "\${CMAKE_INSTALL_PREFIX}/${LIBEXEC_PATH}") + + # we also need a qt.conf in the directory of QtWebProcess + install(CODE " + file(WRITE ${QTWEBPROCESS_PATH}/qt.conf + \"[Paths]\nPlugins = ../PlugIns\nImports = ../Resources/qml\nQml2Imports = ../Resources/qml\" + )" + COMPONENT ${CLIENT_COMPONENT} + ) + + find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH) + + if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD)) + message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin.\ + It is required to produce an relocatable interface application.\ + Check that the environment variable QT_DIR points to your Qt installation.\ + ") + endif () + + install(CODE " + execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\ + \${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/\ + -verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\ + -executable=\${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/Contents/libexec/QtWebProcess\ + )" + COMPONENT ${CLIENT_COMPONENT} + ) + + endif () +endmacro() diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 570e24332b..4d7119d737 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -1,30 +1,78 @@ -# -# GenerateInstallers.cmake -# cmake/macros -# -# Created by Leonardo Murillo on 12/16/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 -# - -macro(GENERATE_INSTALLERS) - if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE AND WIN32) - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/full-stack-deployment") - find_program(MAKENSIS_COMMAND makensis PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS]) - if (NOT MAKENSIS_COMMAND) - message(FATAL_ERROR "The Nullsoft Scriptable Install Systems is required for generating packaged installers on Windows (http://nsis.sourceforge.net/)") - endif () - add_custom_target( - build-package ALL - DEPENDS interface assignment-client domain-server stack-manager - COMMAND set INSTALLER_SOURCE_DIR=${CMAKE_BINARY_DIR}/full-stack-deployment - COMMAND set INSTALLER_NAME=${CMAKE_BINARY_DIR}/${INSTALLER_NAME} - COMMAND set INSTALLER_SCRIPTS_DIR=${CMAKE_SOURCE_DIR}/examples - COMMAND set INSTALLER_COMPANY=${INSTALLER_COMPANY} - COMMAND set INSTALLER_DIRECTORY=${INSTALLER_DIRECTORY} - COMMAND CMD /C "\"${MAKENSIS_COMMAND}\" ${CMAKE_SOURCE_DIR}/tools/nsis/release.nsi" - ) - endif () -endmacro() \ No newline at end of file +# +# GenerateInstallers.cmake +# cmake/macros +# +# Created by Leonardo Murillo on 12/16/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 +# + +macro(GENERATE_INSTALLERS) + include(CPackComponent) + + set(CPACK_MODULE_PATH ${CPACK_MODULE_PATH} "${HF_CMAKE_DIR}/templates") + + set(_DISPLAY_NAME ${BUILD_ORGANIZATION}) + + set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME}) + set(CPACK_PACKAGE_VENDOR "High Fidelity") + set(CPACK_PACKAGE_VERSION ${BUILD_VERSION}) + set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Alpha-${BUILD_VERSION}") + set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) + set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME}) + set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME}) + + if (WIN32) + set(CPACK_NSIS_MUI_ICON "${HF_CMAKE_DIR}/installer/installer.ico") + + # install and reference the Add/Remove icon + set(ADD_REMOVE_ICON_NAME "installer.ico") + set(ADD_REMOVE_ICON_BAD_PATH "${HF_CMAKE_DIR}/installer/${ADD_REMOVE_ICON_NAME}") + fix_path_for_nsis(${ADD_REMOVE_ICON_BAD_PATH} ADD_REMOVE_ICON_PATH) + set(CPACK_NSIS_INSTALLED_ICON_NAME ${ADD_REMOVE_ICON_NAME}) + + # use macro to put backslashes in header image path since nsis requires them + set(_INSTALLER_HEADER_BAD_PATH "${HF_CMAKE_DIR}/installer/installer-header.bmp") + set(INSTALLER_HEADER_IMAGE "") + fix_path_for_nsis(${_INSTALLER_HEADER_BAD_PATH} INSTALLER_HEADER_IMAGE) + + set(_UNINSTALLER_HEADER_BAD_PATH "${HF_CMAKE_DIR}/installer/uninstaller-header.bmp") + set(UNINSTALLER_HEADER_IMAGE "") + fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE) + elseif (APPLE) + # produce a drag and drop DMG on OS X + set(CPACK_GENERATOR "DragNDrop") + + set(CPACK_PACKAGE_INSTALL_DIRECTORY "/") + set(CPACK_PACKAGING_INSTALL_PREFIX /) + set(CPACK_OSX_PACKAGE_VERSION ${CMAKE_OSX_DEPLOYMENT_TARGET}) + + # make sure a High Fidelity directory exists, in case this hits prior to other installs + install(CODE "file(MAKE_DIRECTORY \"\${CMAKE_INSTALL_PREFIX}/${DMG_SUBFOLDER_NAME}\")") + + # add the resource file to the Icon file inside the folder + install(CODE + "execute_process(COMMAND Rez -append ${DMG_SUBFOLDER_ICON} -o \${CMAKE_INSTALL_PREFIX}/${ESCAPED_DMG_SUBFOLDER_NAME}/Icon\\r)" + ) + + # modify the folder to use that custom icon + install(CODE "execute_process(COMMAND SetFile -a C \${CMAKE_INSTALL_PREFIX}/${ESCAPED_DMG_SUBFOLDER_NAME})") + + # hide the special Icon? file + install(CODE "execute_process(COMMAND SetFile -a V \${CMAKE_INSTALL_PREFIX}/${ESCAPED_DMG_SUBFOLDER_NAME}/Icon\\r)") + endif () + + # configure a cpack properties file for custom variables in template + set(CPACK_CONFIGURED_PROP_FILE "${CMAKE_CURRENT_BINARY_DIR}/CPackCustomProperties.cmake") + configure_file("${HF_CMAKE_DIR}/templates/CPackProperties.cmake.in" ${CPACK_CONFIGURED_PROP_FILE}) + set(CPACK_PROPERTIES_FILE ${CPACK_CONFIGURED_PROP_FILE}) + + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") + + cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Client") + cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Server") + + include(CPack) +endmacro() diff --git a/cmake/macros/IncludeApplicationVersion.cmake b/cmake/macros/IncludeApplicationVersion.cmake deleted file mode 100644 index 753a12a01c..0000000000 --- a/cmake/macros/IncludeApplicationVersion.cmake +++ /dev/null @@ -1,36 +0,0 @@ -# -# IncludeApplicationVersion.cmake -# cmake/macros -# -# Created by Leonardo Murillo on 07/14/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 -# - -macro(INCLUDE_APPLICATION_VERSION) - # - # We are relying on Jenkins defined environment variables to determine the origin of this build - # and will only package if this is a PR or Release build - if (DEFINED ENV{JOB_ID}) - set(DEPLOY_PACKAGE 1) - set(BUILD_SEQ $ENV{JOB_ID}) - set(INSTALLER_COMPANY "High Fidelity") - set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}") - set(INSTALLER_NAME "interface-win64-${BUILD_SEQ}.exe") - elseif (DEFINED ENV{ghprbPullId}) - set(DEPLOY_PACKAGE 1) - set(BUILD_SEQ "PR-$ENV{ghprbPullId}") - set(INSTALLER_COMPANY "High Fidelity - PR") - set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}\\${BUILD_SEQ}") - set(INSTALLER_NAME "pr-interface-win64-${BUILD_SEQ}.exe") - else () - set(BUILD_SEQ "dev") - set(INSTALLER_COMPANY "High Fidelity - Dev") - set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}") - set(INSTALLER_NAME "dev-interface-win64.exe") - endif () - configure_file("${MACRO_DIR}/ApplicationVersion.h.in" "${PROJECT_BINARY_DIR}/includes/ApplicationVersion.h") - include_directories("${PROJECT_BINARY_DIR}/includes") -endmacro(INCLUDE_APPLICATION_VERSION) diff --git a/cmake/macros/InstallBesideConsole.cmake b/cmake/macros/InstallBesideConsole.cmake new file mode 100644 index 0000000000..e30a25a0f8 --- /dev/null +++ b/cmake/macros/InstallBesideConsole.cmake @@ -0,0 +1,72 @@ +# +# InstallBesideConsole.cmake +# cmake/macros +# +# Copyright 2016 High Fidelity, Inc. +# Created by Stephen Birarda on January 5th, 2016 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(install_beside_console) + if (WIN32 OR APPLE) + # install this component beside the installed server-console executable + if (APPLE) + set(CONSOLE_APP_CONTENTS "${CONSOLE_INSTALL_APP_PATH}/Contents") + set(COMPONENT_DESTINATION "${CONSOLE_APP_CONTENTS}/MacOS/Components.app/Contents/MacOS") + else () + set(COMPONENT_DESTINATION ${CONSOLE_INSTALL_DIR}) + endif () + + if (APPLE) + install( + TARGETS ${TARGET_NAME} + RUNTIME DESTINATION ${COMPONENT_DESTINATION} + COMPONENT ${SERVER_COMPONENT} + ) + else () + # setup install of executable and things copied by fixup/windeployqt + install( + FILES "$/" + DESTINATION ${COMPONENT_DESTINATION} + COMPONENT ${SERVER_COMPONENT} + ) + + # on windows for PR and production builds, sign the executable + set(EXECUTABLE_COMPONENT ${SERVER_COMPONENT}) + optional_win_executable_signing() + endif () + + if (TARGET_NAME STREQUAL domain-server) + if (APPLE) + set(RESOURCES_DESTINATION ${COMPONENT_DESTINATION}) + else () + set(RESOURCES_DESTINATION ${CONSOLE_INSTALL_DIR}) + endif () + + # install the resources folder for the domain-server where its executable will be + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/resources + DESTINATION ${RESOURCES_DESTINATION} + USE_SOURCE_PERMISSIONS + COMPONENT ${SERVER_COMPONENT} + ) + endif () + + if (APPLE) + # during the install phase, call fixup to drop the shared libraries for these components in the right place + set(EXECUTABLE_NEEDING_FIXUP "\${CMAKE_INSTALL_PREFIX}/${COMPONENT_DESTINATION}/${TARGET_NAME}") + install(CODE " + include(BundleUtilities) + fixup_bundle(\"${EXECUTABLE_NEEDING_FIXUP}\" \"\" \"${FIXUP_LIBS}\") + " COMPONENT ${SERVER_COMPONENT}) + endif () + endif () + + # set variables used by manual ssleay library copy + set(TARGET_INSTALL_DIR ${COMPONENT_DESTINATION}) + set(TARGET_INSTALL_COMPONENT ${SERVER_COMPONENT}) + manually_install_ssl_eay() + +endmacro() diff --git a/cmake/macros/ManuallyInstallSSLEay.cmake b/cmake/macros/ManuallyInstallSSLEay.cmake new file mode 100644 index 0000000000..41e7e9eaf3 --- /dev/null +++ b/cmake/macros/ManuallyInstallSSLEay.cmake @@ -0,0 +1,28 @@ +# +# ManuallyInstallSSLEay.cmake +# +# Created by Stephen Birarda on 1/15/16. +# 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 +# + +macro(manually_install_ssl_eay) + + # on windows we have had issues with targets missing ssleay library + # not convinced we actually need it (I assume it would show up in the dependency tree) + # but to be safe we install it manually beside the current target + if (WIN32) + # we need to find SSL_EAY_LIBRARY_* so we can install it beside this target + # so we have to call find_package(OpenSSL) here even though this target may not specifically need it + find_package(OpenSSL REQUIRED) + + install( + FILES "${OPENSSL_DLL_PATH}/ssleay32.dll" + DESTINATION ${TARGET_INSTALL_DIR} + COMPONENT ${TARGET_INSTALL_COMPONENT} + ) + endif() + +endmacro() diff --git a/cmake/macros/OptionalWinExecutableSigning.cmake b/cmake/macros/OptionalWinExecutableSigning.cmake new file mode 100644 index 0000000000..784aae716f --- /dev/null +++ b/cmake/macros/OptionalWinExecutableSigning.cmake @@ -0,0 +1,34 @@ +# +# OptionalWinExecutableSigning.cmake +# cmake/macros +# +# Copyright 2016 High Fidelity, Inc. +# Created by Stephen Birarda on January 12, 2016 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(optional_win_executable_signing) + if (WIN32 AND PRODUCTION_BUILD) + if (DEFINED ENV{HF_PFX_FILE}) + if (DEFINED ENV{HF_PFX_PASSPHRASE}) + message(STATUS "Executable for ${TARGET_NAME} will be signed with SignTool.") + + if (NOT EXECUTABLE_PATH) + set(EXECUTABLE_PATH "$") + endif () + + # setup a post build command to sign the executable + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${SIGNTOOL_EXECUTABLE} sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} + ) + else () + message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.") + endif () + else () + message(WARNING "Creating a production build but not code signing since HF_PFX_FILE is not set.") + endif () + endif () +endmacro() diff --git a/cmake/macros/PackageLibrariesForDeployment.cmake b/cmake/macros/PackageLibrariesForDeployment.cmake index 17b5d5f49d..050cea9fe1 100644 --- a/cmake/macros/PackageLibrariesForDeployment.cmake +++ b/cmake/macros/PackageLibrariesForDeployment.cmake @@ -1,25 +1,24 @@ -# +# # PackageLibrariesForDeployment.cmake # cmake/macros -# +# # Copyright 2015 High Fidelity, Inc. # Created by Stephen Birarda on February 17, 2014 # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT) - if (WIN32) configure_file( - ${HIFI_CMAKE_DIR}/templates/FixupBundlePostBuild.cmake.in + ${HF_CMAKE_DIR}/templates/FixupBundlePostBuild.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FixupBundlePostBuild.cmake @ONLY ) - + set(PLUGIN_PATH "plugins") - + # add a post-build command to copy DLLs beside the executable add_custom_command( TARGET ${TARGET_NAME} @@ -29,31 +28,18 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT) -DBUNDLE_PLUGIN_DIR=$/${PLUGIN_PATH} -P ${CMAKE_CURRENT_BINARY_DIR}/FixupBundlePostBuild.cmake ) - + find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) - + if (NOT WINDEPLOYQT_COMMAND) message(FATAL_ERROR "Could not find windeployqt at ${QT_DIR}/bin. windeployqt is required.") endif () - + # add a post-build command to call windeployqt to copy Qt plugins add_custom_command( TARGET ${TARGET_NAME} POST_BUILD COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release> $" ) - elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) - find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) - - if (NOT MACDEPLOYQT_COMMAND) - message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin. macdeployqt is required.") - endif () - - # add a post-build command to call macdeployqt to copy Qt plugins - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${MACDEPLOYQT_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/\${CONFIGURATION}/${TARGET_NAME}.app -verbose 0 - ) endif () endmacro() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake new file mode 100644 index 0000000000..5bfbb0794d --- /dev/null +++ b/cmake/macros/SetPackagingParameters.cmake @@ -0,0 +1,109 @@ +# +# SetPackagingParameters.cmake +# cmake/macros +# +# Created by Leonardo Murillo on 07/14/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 + +# This macro checks some Jenkins defined environment variables to determine the origin of this build +# and decides how targets should be packaged. + +macro(SET_PACKAGING_PARAMETERS) + set(PR_BUILD 0) + set(PRODUCTION_BUILD 0) + set(DEV_BUILD 0) + + set(RELEASE_TYPE $ENV{RELEASE_TYPE}) + set(RELEASE_NUMBER $ENV{RELEASE_NUMBER}) + + if (RELEASE_TYPE STREQUAL "PRODUCTION") + set(DEPLOY_PACKAGE TRUE) + set(PRODUCTION_BUILD 1) + set(BUILD_VERSION ${RELEASE_NUMBER}) + set(BUILD_ORGANIZATION "High Fidelity") + set(HIGH_FIDELITY_PROTOCOL "hifi") + set(INTERFACE_BUNDLE_NAME "High Fidelity") + set(INTERFACE_ICON_PREFIX "interface") + elseif (RELEASE_TYPE STREQUAL "PR") + set(DEPLOY_PACKAGE TRUE) + set(PR_BUILD 1) + set(BUILD_VERSION "PR${RELEASE_NUMBER}") + set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}") + set(INTERFACE_BUNDLE_NAME "High Fidelity") + set(INTERFACE_ICON_PREFIX "interface-beta") + else () + set(DEV_BUILD 1) + set(BUILD_VERSION "dev") + set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}") + set(INTERFACE_BUNDLE_NAME "Interface") + set(INTERFACE_ICON_PREFIX "interface-beta") + endif () + + if (APPLE) + set(DMG_SUBFOLDER_NAME "${BUILD_ORGANIZATION}") + + set(ESCAPED_DMG_SUBFOLDER_NAME "") + string(REPLACE " " "\\ " ESCAPED_DMG_SUBFOLDER_NAME ${DMG_SUBFOLDER_NAME}) + + set(DMG_SUBFOLDER_ICON "${HF_CMAKE_DIR}/installer/install-folder.rsrc") + + set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + else () + set(CONSOLE_INSTALL_DIR ".") + set(INTERFACE_INSTALL_DIR ".") + endif () + + if (WIN32) + set(INTERFACE_EXEC_PREFIX "interface") + set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.ico") + + set(CONSOLE_EXEC_NAME "server-console.exe") + + set(DS_EXEC_NAME "domain-server.exe") + set(AC_EXEC_NAME "assignment-client.exe") + + # start menu shortcuts + set(INTERFACE_SM_SHORTCUT_NAME "High Fidelity") + set(CONSOLE_SM_SHORTCUT_NAME "Server Console") + + # check if we need to find signtool + if (PRODUCTION_BUILD OR PR_BUILD) + find_program(SIGNTOOL_EXECUTABLE signtool PATHS "C:/Program Files (x86)/Windows Kits/8.1" PATH_SUFFIXES "bin/x64") + + if (NOT SIGNTOOL_EXECUTABLE) + message(FATAL_ERROR "Code signing of executables was requested but signtool.exe could not be found.") + endif () + endif () + + set(GENERATED_UNINSTALLER_EXEC_NAME "Uninstall.exe") + set(REGISTRY_HKLM_INSTALL_ROOT "Software") + set(POST_INSTALL_OPTIONS_REG_GROUP "PostInstallOptions") + set(CLIENT_DESKTOP_SHORTCUT_REG_KEY "ClientDesktopShortcut") + set(CONSOLE_DESKTOP_SHORTCUT_REG_KEY "ConsoleDesktopShortcut") + set(CONSOLE_STARTUP_REG_KEY "ConsoleStartupShortcut") + set(LAUNCH_NOW_REG_KEY "LaunchAfterInstall") + endif () + + if (APPLE) + + set(CONSOLE_EXEC_NAME "Server Console.app") + set(CONSOLE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${CONSOLE_EXEC_NAME}") + + set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app") + set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns") + endif() + + # setup component categories for installer + set(DDE_COMPONENT dde) + set(CLIENT_COMPONENT client) + set(SERVER_COMPONENT server) + + # create a header file our targets can use to find out the application version + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/includes") + configure_file("${HF_CMAKE_DIR}/templates/BuildInfo.h.in" "${CMAKE_BINARY_DIR}/includes/BuildInfo.h") + +endmacro(SET_PACKAGING_PARAMETERS) diff --git a/cmake/macros/SetupHifiPlugin.cmake b/cmake/macros/SetupHifiPlugin.cmake index b9fc4490d7..e9c8688590 100644 --- a/cmake/macros/SetupHifiPlugin.cmake +++ b/cmake/macros/SetupHifiPlugin.cmake @@ -12,12 +12,12 @@ macro(SETUP_HIFI_PLUGIN) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") if (APPLE) - set(PLUGIN_PATH "interface.app/Contents/MacOS/plugins") + set(PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns") else() set(PLUGIN_PATH "plugins") endif() - IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles") set(PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${PLUGIN_PATH}/") else() set(PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${PLUGIN_PATH}/") @@ -35,5 +35,4 @@ macro(SETUP_HIFI_PLUGIN) "$" ${PLUGIN_FULL_PATH} ) - endmacro() diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 77df280d1a..6c150b6c8d 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -1,36 +1,35 @@ -# +# # SetupHifiProject.cmake -# +# # Copyright 2013 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(SETUP_HIFI_PROJECT) project(${TARGET_NAME}) - + # grab the implemenation and header files file(GLOB TARGET_SRCS src/*) - + file(GLOB SRC_SUBDIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/*) - + foreach(DIR ${SRC_SUBDIRS}) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/${DIR}") file(GLOB DIR_CONTENTS "src/${DIR}/*") set(TARGET_SRCS ${TARGET_SRCS} "${DIR_CONTENTS}") endif () endforeach() - - if (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) - add_executable(${TARGET_NAME} MACOSX_BUNDLE ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) - else () - add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) - endif() + + add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) + + # include the generated application version header + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN}) list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core) - + # find these Qt modules and link them to our own target find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED) @@ -44,7 +43,7 @@ macro(SETUP_HIFI_PROJECT) foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES}) target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE}) endforeach() - + target_glm() - + endmacro() diff --git a/cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake b/cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake index fe6f5b671e..37a7a9caa0 100644 --- a/cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake +++ b/cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake @@ -1,5 +1,5 @@ # -# CopyDirectoryBesideTarget.cmake +# CopyDirectoryBesideTarget.cmake # cmake/macros # # Created by Stephen Birarda on 04/30/15. @@ -10,22 +10,22 @@ # 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 $/${_DESTINATION} ) - + if (${_SHOULD_SYMLINK}) - + # first create the destination add_custom_command( TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory $/${_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 @@ -35,20 +35,20 @@ macro(SYMLINK_OR_COPY_DIRECTORY_BESIDE_TARGET _SHOULD_SYMLINK _DIRECTORY _DESTIN 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} $/${_DESTINATION}/${_ITEM_FILENAME} - ) - endforeach() + ) + endforeach() else () # copy the directory add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${_DIRECTORY} + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_DIRECTORY} $/${_DESTINATION} ) endif () diff --git a/cmake/macros/TargetNsight.cmake b/cmake/macros/TargetNsight.cmake index 4b7e87e9d3..09b056d07a 100644 --- a/cmake/macros/TargetNsight.cmake +++ b/cmake/macros/TargetNsight.cmake @@ -1,20 +1,28 @@ -# +# # Copyright 2015 High Fidelity, Inc. # Created by Bradley Austin Davis on 2015/10/10 # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# macro(TARGET_NSIGHT) - if (WIN32) - if (USE_NSIGHT) + if (WIN32 AND USE_NSIGHT) + + # grab the global CHECKED_FOR_NSIGHT_ONCE property + get_property(NSIGHT_CHECKED GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE) + + if (NOT NSIGHT_CHECKED) # try to find the Nsight package and add it to the build if we find it find_package(NSIGHT) - if (NSIGHT_FOUND) - include_directories(${NSIGHT_INCLUDE_DIRS}) - add_definitions(-DNSIGHT_FOUND) - target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") - endif () - endif() - endif (WIN32) -endmacro() \ No newline at end of file + + # set the global CHECKED_FOR_NSIGHT_ONCE property so that we only debug that we couldn't find it once + set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE) + endif () + + if (NSIGHT_FOUND) + include_directories(${NSIGHT_INCLUDE_DIRS}) + add_definitions(-DNSIGHT_FOUND) + target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") + endif () + endif () +endmacro() diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake deleted file mode 100644 index 0536a1a9d8..0000000000 --- a/cmake/macros/TargetQuazip.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2015 High Fidelity, Inc. -# Created by Leonardo Murillo on 2015/11/20 -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# -macro(TARGET_QUAZIP) - add_dependency_external_projects(quazip) - find_package(QuaZip REQUIRED) - target_include_directories(${TARGET_NAME} PUBLIC ${QUAZIP_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) - if (WIN32) - add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) - endif () -endmacro() diff --git a/cmake/macros/TargetSixense.cmake b/cmake/macros/TargetSixense.cmake index b52af9cdd2..6fd9cede1f 100644 --- a/cmake/macros/TargetSixense.cmake +++ b/cmake/macros/TargetSixense.cmake @@ -1,14 +1,14 @@ -# +# # Copyright 2015 High Fidelity, Inc. # Created by Bradley Austin Davis on 2015/10/10 # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# macro(TARGET_SIXENSE) add_dependency_external_projects(sixense) find_package(Sixense REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) add_definitions(-DHAVE_SIXENSE) -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 9d9557ad9e..5cc48d2598 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -8,11 +8,11 @@ # OPENSSL_INCLUDE_DIR - the OpenSSL include directory # OPENSSL_LIBRARIES - The libraries needed to use OpenSSL # OPENSSL_VERSION - This is set to $major.$minor.$revision$path (eg. 0.9.8s) -# +# # Modified on 7/16/2014 by Stephen Birarda # This is an adapted version of the FindOpenSSL.cmake module distributed with Cmake 2.8.12.2 # The original license for that file is displayed below. -# +# #============================================================================= # Copyright 2006-2009 Kitware, Inc. # Copyright 2006 Alexander Neundorf @@ -50,18 +50,18 @@ if (WIN32) ) set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win32" "C:/OpenSSL/" "C:/OpenSSL-Win32/") endif() - + unset(_programfiles) set(_OPENSSL_ROOT_HINTS_AND_PATHS HINTS ${_OPENSSL_ROOT_HINTS} PATHS ${_OPENSSL_ROOT_PATHS}) - + else () include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("openssl") - + set(_OPENSSL_ROOT_HINTS_AND_PATHS ${OPENSSL_SEARCH_DIRS}) endif () -find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_INCLUDEDIR} +find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_INCLUDEDIR} PATH_SUFFIXES include ) @@ -81,15 +81,15 @@ if (WIN32 AND NOT CYGWIN) # We are using the libraries located in the VC subdir instead of the parent directory eventhough : # libeay32MD.lib is identical to ../libeay32.lib, and # ssleay32MD.lib is identical to ../ssleay32.lib - find_library(LIB_EAY_DEBUG NAMES libeay32MDd libeay32d + find_library(LIB_EAY_DEBUG NAMES libeay32MDd libeay32d ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" ) - find_library(LIB_EAY_RELEASE NAMES libeay32MD libeay32 + find_library(LIB_EAY_RELEASE NAMES libeay32MD libeay32 ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" ) - find_library(SSL_EAY_DEBUG NAMES ssleay32MDd ssleay32d + find_library(SSL_EAY_DEBUG NAMES ssleay32MDd ssleay32d ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" ) @@ -109,22 +109,22 @@ if (WIN32 AND NOT CYGWIN) set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) - + elseif (MINGW) # same player, for MinGW set(LIB_EAY_NAMES libeay32) set(SSL_EAY_NAMES ssleay32) - + if (CMAKE_CROSSCOMPILING) list(APPEND LIB_EAY_NAMES crypto) list(APPEND SSL_EAY_NAMES ssl) endif () - + find_library(LIB_EAY NAMES ${LIB_EAY_NAMES} ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "lib/MinGW" ) - find_library(SSL_EAY NAMES ${SSL_EAY_NAMES} + find_library(SSL_EAY NAMES ${SSL_EAY_NAMES} ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "lib/MinGW" ) @@ -147,7 +147,7 @@ else() PATH_SUFFIXES lib ) - find_library(OPENSSL_CRYPTO_LIBRARY NAMES crypto HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_LIBDIR} + find_library(OPENSSL_CRYPTO_LIBRARY NAMES crypto HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_LIBDIR} PATH_SUFFIXES lib ) @@ -196,7 +196,7 @@ if (OPENSSL_INCLUDE_DIR) if(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h") file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str REGEX "^#[ ]?define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*") - + # The version number is encoded as 0xMNNFFPPS: major minor fix patch status # The status gives if this is a developer or prerelease and is ignored here. # Major, minor, and fix directly translate into the version numbers shown in @@ -252,15 +252,6 @@ endif () if (WIN32) add_paths_to_fixup_libs(${OPENSSL_DLL_PATH}) - # - # For some reason fixup misses the following DLL and only copies libeay32. There's gotta be a better way to handle this - # but for now resorting to the following interm solution - if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE) - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy ${OPENSSL_DLL_PATH}/ssleay32.dll ${CMAKE_BINARY_DIR}/full-stack-deployment/ - ) - endif () endif () mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES OPENSSL_SEARCH_DIRS) diff --git a/cmake/modules/FindQuaZip.cmake b/cmake/modules/FindQuaZip.cmake deleted file mode 100644 index 110f239c68..0000000000 --- a/cmake/modules/FindQuaZip.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# -# FindQuaZip.h -# StackManagerQt/cmake/modules -# -# Created by Mohammed Nafees. -# Copyright (c) 2014 High Fidelity. All rights reserved. -# - -# QUAZIP_FOUND - QuaZip library was found -# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir -# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR) -# QUAZIP_LIBRARIES - List of QuaZip libraries -# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers - -include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") -hifi_library_search_hints("quazip") - -if (WIN32) - find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS}) -elseif (APPLE) - find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS}) -else () - find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES quazip HINTS ${QUAZIP_SEARCH_DIRS}) -endif () - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_INCLUDE_DIRS) - -mark_as_advanced(QUAZIP_INCLUDE_DIRS QUAZIP_SEARCH_DIRS) diff --git a/cmake/macros/ApplicationVersion.h.in b/cmake/templates/BuildInfo.h.in similarity index 51% rename from cmake/macros/ApplicationVersion.h.in rename to cmake/templates/BuildInfo.h.in index 736d00726c..3a462feb1a 100644 --- a/cmake/macros/ApplicationVersion.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -1,12 +1,15 @@ // -// ApplicationVersion.h.in +// BuildInfo.h.in // cmake/macros // -// Created by Leonardo Murillo on 8/13/15. +// Created by Stephen Birarda on 1/14/16. // 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 // -const QString BUILD_VERSION = "@BUILD_SEQ@"; +namespace BuildInfo { + const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@"; + const QString VERSION = "@BUILD_VERSION@"; +} diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in new file mode 100644 index 0000000000..7b3d0ae53e --- /dev/null +++ b/cmake/templates/CPackProperties.cmake.in @@ -0,0 +1,35 @@ +# +# CPackProperties.cmake.in +# cmake/templates +# +# Copyright 2016 High Fidelity, Inc. +# Created by Stephen Birarda on January 11, 2016 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +set(INTERFACE_SHORTCUT_NAME "@INTERFACE_SM_SHORTCUT_NAME@") +set(INTERFACE_WIN_EXEC_NAME "@INTERFACE_EXEC_PREFIX@.exe") +set(CONSOLE_SHORTCUT_NAME "@CONSOLE_SM_SHORTCUT_NAME@") +set(CONSOLE_WIN_EXEC_NAME "@CONSOLE_EXEC_NAME@") +set(DS_EXEC_NAME "@DS_EXEC_NAME@") +set(AC_EXEC_NAME "@AC_EXEC_NAME@") +set(HIGH_FIDELITY_PROTOCOL "@HIGH_FIDELITY_PROTOCOL@") +set(PRODUCTION_BUILD "@PRODUCTION_BUILD@") +set(PR_BUILD "@PR_BUILD@") +set(BUILD_ORGANIZATION "@BUILD_ORGANIZATION@") +set(POST_INSTALL_OPTIONS_PATH "@POST_INSTALL_OPTIONS_PATH@") +set(CLIENT_COMPONENT_NAME "@CLIENT_COMPONENT@") +set(SERVER_COMPONENT_NAME "@SERVER_COMPONENT@") +set(SIGNTOOL_EXECUTABLE "@SIGNTOOL_EXECUTABLE@") +set(UNINSTALLER_NAME "@GENERATED_UNINSTALLER_EXEC_NAME@") +set(REGISTRY_HKLM_INSTALL_ROOT "@REGISTRY_HKLM_INSTALL_ROOT@") +set(POST_INSTALL_OPTIONS_REG_GROUP "@POST_INSTALL_OPTIONS_REG_GROUP@") +set(CLIENT_DESKTOP_SHORTCUT_REG_KEY "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@") +set(CONSOLE_DESKTOP_SHORTCUT_REG_KEY "@CONSOLE_DESKTOP_SHORTCUT_REG_KEY@") +set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@") +set(LAUNCH_NOW_REG_KEY "@LAUNCH_NOW_REG_KEY@") +set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@") +set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@") +set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@") diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 6e3ad9723b..57d1fd787f 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -1,56 +1,54 @@ -# +# # FixupBundlePostBuild.cmake.in # cmake/templates -# +# # Copyright 2015 High Fidelity, Inc. # Created by Stephen Birarda on February 13, 2014 # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# include(BundleUtilities) - -# replace copy_resolved_item_into_bundle +# replace copy_resolved_item_into_bundle # # The official version of copy_resolved_item_into_bundle will print out a "warning:" when # the resolved item matches the resolved embedded item. This not not really an issue that # should rise to the level of a "warning" so we replace this message with a "status:" # function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) - if(WIN32) - # ignore case on Windows - string(TOLOWER "${resolved_item}" resolved_item_compare) - string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) - else() - set(resolved_item_compare "${resolved_item}") - set(resolved_embedded_item_compare "${resolved_embedded_item}") - endif() + if (WIN32) + # ignore case on Windows + string(TOLOWER "${resolved_item}" resolved_item_compare) + string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) + else() + set(resolved_item_compare "${resolved_item}") + set(resolved_embedded_item_compare "${resolved_embedded_item}") + endif() - if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") - # this is our only change from the original version - message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") - else() - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") - endif() + if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") + # this is our only change from the original version + message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") + else() + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") + if(UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") endif() + endif() endfunction() message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@") - message(STATUS "Scanning for plugins from ${BUNDLE_PLUGIN_DIR}") if (APPLE) - set(PLUGIN_EXTENSION "dylib") -elseif (WIN32) - set(PLUGIN_EXTENSION "dll") -else() - set(PLUGIN_EXTENSION "so") + set(PLUGIN_EXTENSION "dylib") +elseif (WIN32) + set(PLUGIN_EXTENSION "dll") +else() + set(PLUGIN_EXTENSION "so") endif() -file(GLOB RUNTIME_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}") -fixup_bundle("${BUNDLE_EXECUTABLE}" "${RUNTIME_PLUGINS}" "@FIXUP_LIBS@") +file(GLOB EXTRA_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}") +fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@") diff --git a/cmake/templates/Icon.rc.in b/cmake/templates/Icon.rc.in new file mode 100644 index 0000000000..d5f6ca74d6 --- /dev/null +++ b/cmake/templates/Icon.rc.in @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "@CONFIGURE_ICON_PATH@" diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in new file mode 100644 index 0000000000..075b3df38b --- /dev/null +++ b/cmake/templates/NSIS.template.in @@ -0,0 +1,1364 @@ +; CPack install script designed for a nmake build + +;-------------------------------- +; You must define these values + + !define VERSION "@CPACK_PACKAGE_VERSION@" + !define PATCH "@CPACK_PACKAGE_VERSION_PATCH@" + !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" + +;-------------------------------- +;Variables + + Var MUI_TEMP + Var STARTMENU_FOLDER + Var SV_ALLUSERS + Var START_MENU + Var DO_NOT_ADD_TO_PATH + Var ADD_TO_PATH_ALL_USERS + Var ADD_TO_PATH_CURRENT_USER + Var INSTALL_DESKTOP + Var IS_DEFAULT_INSTALLDIR + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + !include "InstallOptions.nsh" + + ;Default installation folder + InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + +;-------------------------------- +;General + ; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges + !include UAC.nsh + + ; Set name prior to inner loop so uninstaller has correct values + Name "@CPACK_NSIS_PACKAGE_NAME@" + BrandingText " " + ManifestDPIAware true + + !ifdef INNER + !echo "Inner invocation" ; just to see what's going on + + ; Require user only for temp installer + RequestExecutionLevel user + + OutFile "$%TEMP%\tempinstaller.exe" ; not really important where this is + SetCompress off ; for speed + !else + !echo "Outer invocation" + + ; Require administrator access + RequestExecutionLevel admin + + ; Call makensis again, defining INNER. This writes an installer for us which, when + ; it is invoked, will just write the uninstaller to some location, and then exit. + ; Be sure to substitute the name of this script here. + + !system "$\"${NSISDIR}\makensis$\" /DINNER project.nsi" = 0 + + ; Require administrator access + RequestExecutionLevel admin + + ; So now run that installer we just created as %TEMP%\tempinstaller.exe. Since it + ; calls quit the return value isn't zero. + + !system "$%TEMP%\tempinstaller.exe" = 2 + + ; The Inner invocation has written an uninstaller binary for us. + ; We need to sign it if it's a production or PR build. + !if @PRODUCTION_BUILD@ == 1 + !system '"@SIGNTOOL_EXECUTABLE@" sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !endif + + ; Good. Now we can carry on writing the real installer. + + ;Output file + OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" + + ;Set compression + SetCompressor @CPACK_NSIS_COMPRESSOR@ + !endif + +@CPACK_NSIS_DEFINES@ + + !include Sections.nsh + +;--- Component support macros: --- +; The code for the add/remove functionality is from: +; http://nsis.sourceforge.net/Add/Remove_Functionality +; It has been modified slightly and extended to provide +; inter-component dependencies. +Var AR_SecFlags +Var AR_RegFlags +@CPACK_NSIS_SECTION_SELECTED_VARS@ + +; Loads the "selected" flag for the section named SecName into the +; variable VarName. +!macro LoadSectionSelectedIntoVar SecName VarName + SectionGetFlags ${${SecName}} $${VarName} + IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits +!macroend + +; Loads the value of a variable... can we get around this? +!macro LoadVar VarName + IntOp $R0 0 + $${VarName} +!macroend + +; Sets the value of a variable +!macro StoreVar VarName IntValue + IntOp $${VarName} 0 + ${IntValue} +!macroend + +!macro InitSection SecName + ; This macro reads component installed flag from the registry and + ;changes checked state of the section on the components page. + ;Input: section index constant name specified in Section command. + + ClearErrors + ;Reading component status from registry + ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" "Installed" + IfErrors "default_${SecName}" + ;Status will stay default if registry value not found + ;(component was never installed) + IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags + IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off + IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit + + ; Note whether this component was installed before + !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags + IntOp $R0 $AR_RegFlags & $AR_RegFlags + + ;Writing modified flags + SectionSetFlags ${${SecName}} $AR_SecFlags + + "default_${SecName}:" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected +!macroend + +!macro FinishSection SecName + ; This macro reads section flag set by user and removes the section + ;if it is not selected. + ;Then it writes component installed flag to registry + ;Input: section index constant name specified in Section command. + + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags + ;Checking lowest bit: + IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + IntCmp $AR_SecFlags 1 "leave_${SecName}" + ;Section is not selected: + + ; NOTE: The default CPack template calls Section uninstall macro and writes zero installed flag + ; We do not do that here because it would absolutely cause issues with shared dependencies + + ;!insertmacro "Remove_${${SecName}}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 0 + + Goto "exit_${SecName}" + + "leave_${SecName}:" + ;Section is selected: + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 1 + + "exit_${SecName}:" +!macroend + +!macro RemoveSection_CPack SecName + ; This macro is used to call section's Remove_... macro + ;from the uninstaller. + ;Input: section index constant name specified in Section command. + + !insertmacro "Remove_${${SecName}}" +!macroend + +; Determine whether the selection of SecName changed +!macro MaybeSelectionChanged SecName + !insertmacro LoadVar ${SecName}_selected + SectionGetFlags ${${SecName}} $R1 + IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits + + ; See if the status has changed: + IntCmp $R0 $R1 "${SecName}_unchanged" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected + + IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected" + !insertmacro "Deselect_required_by_${SecName}" + goto "${SecName}_unchanged" + + "${SecName}_was_selected:" + !insertmacro "Select_${SecName}_depends" + + "${SecName}_unchanged:" +!macroend +;--- End of Add/Remove macros --- + +;-------------------------------- +;Interface Settings + + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "@INSTALLER_HEADER_IMAGE@" + !define MUI_HEADERIMAGE_UNBITMAP "@UNINSTALLER_HEADER_IMAGE@" + !define MUI_ABORTWARNING + +;-------------------------------- +; path functions + +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 + +;---------------------------------------- +; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02" +;---------------------------------------- +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 +;==================================================== +; get_NT_environment +; Returns: the selected environment +; Output : head of the stack +;==================================================== +!macro select_NT_profile UN +Function ${UN}select_NT_profile + StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single + DetailPrint "Selected environment for all users" + Push "all" + Return + environment_single: + DetailPrint "Selected environment for current user only." + Push "current" + Return +FunctionEnd +!macroend +!insertmacro select_NT_profile "" +!insertmacro select_NT_profile "un." +;---------------------------------------------------- +!define NT_current_env 'HKCU "Environment"' +!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +!ifndef WriteEnvStr_RegKey + !ifdef ALL_USERS + !define WriteEnvStr_RegKey \ + 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !else + !define WriteEnvStr_RegKey 'HKCU "Environment"' + !endif +!endif + +; AddToPath - Adds the given dir to the search path. +; Input - head of the stack +; Note - Win9x systems requires reboot + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + + # don't add if the path doesn't exist + IfFileExists "$0\*.*" "" AddToPath_done + + ReadEnvStr $1 PATH + ; if the path is too long for a NSIS variable NSIS will return a 0 + ; length string. If we find that, then warn and skip any path + ; modification as it will trash the existing path. + StrLen $2 $1 + IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done + CheckPathLength_ShowPathWarning: + Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!" + Goto AddToPath_done + CheckPathLength_Done: + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + GetFullPathName /SHORT $3 $0 + Push "$1;" + Push "$3;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$3\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + + Call IsNT + Pop $1 + StrCmp $1 1 AddToPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" a + FileSeek $1 -1 END + FileReadByte $1 $2 + IntCmp $2 26 0 +2 +2 # DOS EOF + FileSeek $1 -1 END # write over EOF + FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" + FileClose $1 + SetRebootFlag true + Goto AddToPath_done + + AddToPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto DoTrim + ReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + DoTrim: + StrCmp $1 "" AddToPath_NTdoIt + Push $1 + Call Trim + Pop $1 + StrCpy $0 "$1;$0" + AddToPath_NTdoIt: + StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $0 + Goto DoSend + WriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $0 + DoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + AddToPath_done: + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Remove a given dir from the path +; Input: head of the stack + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + IntFmt $6 "%c" 26 # DOS EOF + + Call un.IsNT + Pop $1 + StrCmp $1 1 unRemoveFromPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" r + GetTempFileName $4 + FileOpen $2 $4 w + GetFullPathName /SHORT $0 $0 + StrCpy $0 "SET PATH=%PATH%;$0" + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoop: + FileRead $1 $3 + StrCpy $5 $3 1 -1 # read last char + StrCmp $5 $6 0 +2 # if DOS EOF + StrCpy $3 $3 -1 # remove DOS EOF so we can compare + StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "" unRemoveFromPath_dosLoopEnd + FileWrite $2 $3 + Goto unRemoveFromPath_dosLoop + unRemoveFromPath_dosLoopRemoveLine: + SetRebootFlag true + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoopEnd: + FileClose $2 + FileClose $1 + StrCpy $1 $WINDIR 2 + Delete "$1\autoexec.bat" + CopyFiles /SILENT $4 "$1\autoexec.bat" + Delete $4 + Goto unRemoveFromPath_done + + unRemoveFromPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto unDoTrim + unReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + unDoTrim: + StrCpy $5 $1 1 -1 # copy last char + StrCmp $5 ";" +2 # if last char != ; + StrCpy $1 "$1;" # append ; + Push $1 + Push "$0;" + Call un.StrStr ; Find `$0;` in $1 + Pop $2 ; pos of our dir + StrCmp $2 "" unRemoveFromPath_done + ; else, it is in path + # $0 - path to add + # $1 - path var + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 # $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove + StrCpy $3 $5$6 + + StrCpy $5 $3 1 -1 # copy last char + StrCmp $5 ";" 0 +2 # if last char == ; + StrCpy $3 $3 -1 # remove last char + + StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $3 + Goto unDoSend + unWriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $3 + unDoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + unRemoveFromPath_done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Uninstall sutff +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +########################################### +# Utility Functions # +########################################### + +;==================================================== +; IsNT - Returns 1 if the current system is NT, 0 +; otherwise. +; Output: head of the stack +;==================================================== +; IsNT +; no input +; output, top of the stack = 1 if NT or 0 if not +; +; Usage: +; Call IsNT +; Pop $R0 +; ($R0 at this point is 1 or 0) + +!macro IsNT un +Function ${un}IsNT + Push $0 + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $0 "" 0 IsNT_yes + ; we are not NT. + Pop $0 + Push 0 + Return + + IsNT_yes: + ; NT!!! + Pop $0 + Push 1 +FunctionEnd +!macroend +!insertmacro IsNT "" +!insertmacro IsNT "un." + +; StrStr +; input, top of stack = string to search for +; top of stack-1 = string to search in +; output, top of stack (replaces with the portion of the string remaining) +; modifies no other variables. +; +; Usage: +; Push "this is a long ass string" +; Push "ass" +; Call StrStr +; Pop $R0 +; ($R0 at this point is "ass string") + +!macro StrStr un +Function ${un}StrStr +Exch $R1 ; st=haystack,old$R1, $R1=needle + Exch ; st=old$R1,haystack + Exch $R2 ; st=old$R1,old$R2, $R2=haystack + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=needle + ; $R2=haystack + ; $R3=len(needle) + ; $R4=cnt + ; $R5=tmp + loop: + StrCpy $R5 $R2 $R3 $R4 + StrCmp $R5 $R1 done + StrCmp $R5 "" done + IntOp $R4 $R4 + 1 + Goto loop +done: + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." + +Function Trim ; Added by Pelaca + Exch $R1 + Push $R2 +Loop: + StrCpy $R2 "$R1" 1 -1 + StrCmp "$R2" " " RTrim + StrCmp "$R2" "$\n" RTrim + StrCmp "$R2" "$\r" RTrim + StrCmp "$R2" ";" RTrim + GoTo Done +RTrim: + StrCpy $R1 "$R1" -1 + Goto Loop +Done: + Pop $R2 + Exch $R1 +FunctionEnd + +Function ConditionalAddToRegisty + Pop $0 + Pop $1 + StrCmp "$0" "" ConditionalAddToRegisty_EmptyString + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ + "$1" "$0" + ;MessageBox MB_OK "Set Registry: '$1' to '$0'" + DetailPrint "Set install registry entry: '$1' to '$0'" + ConditionalAddToRegisty_EmptyString: +FunctionEnd + +;-------------------------------- + +!ifdef CPACK_USES_DOWNLOAD +Function DownloadFile + IfFileExists $INSTDIR\* +2 + CreateDirectory $INSTDIR + Pop $0 + + ; Skip if already downloaded + IfFileExists $INSTDIR\$0 0 +2 + Return + + StrCpy $1 "@CPACK_DOWNLOAD_SITE@" + + try_again: + NSISdl::download "$1/$0" "$INSTDIR\$0" + + Pop $1 + StrCmp $1 "success" success + StrCmp $1 "Cancelled" cancel + MessageBox MB_OK "Download failed: $1" + cancel: + Return + success: +FunctionEnd +!endif + +;-------------------------------- +; Installation types +@CPACK_NSIS_INSTALLATION_TYPES@ + +;-------------------------------- +; Component sections +@CPACK_NSIS_COMPONENT_SECTIONS@ + +;-------------------------------- +; Define some macro setting for the gui +@CPACK_NSIS_INSTALLER_MUI_ICON_CODE@ +@CPACK_NSIS_INSTALLER_ICON_CODE@ +@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@ +@CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE@ + +;-------------------------------- +;Pages + !insertmacro MUI_PAGE_WELCOME + + !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" + Page custom InstallOptionsPage + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + + @CPACK_NSIS_PAGE_COMPONENTS@ + + !insertmacro MUI_PAGE_INSTFILES + Page custom PostInstallOptionsPage HandlePostInstallOptions + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" ;first language is the default language + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Basque" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Irish" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Kurdish" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Welsh" + + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "NSIS.InstallOptions.ini" + ReserveFile "@POST_INSTALL_OPTIONS_PATH@" + +;-------------------------------- +;Installer Sections + +Section "-Core installation" + ;Use the entire tree produced by the INSTALL target. Keep the + ;list of directories here in sync with the RMDir commands below. + SetOutPath "$INSTDIR" + @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@ + @CPACK_NSIS_FULL_INSTALL@ + + ;Store installation folder + WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + + ;Package the signed uninstaller produced by the inner loop + !ifndef INNER + ; this packages the signed uninstaller + File $%TEMP%\@UNINSTALLER_NAME@ + !endif + + Push "DisplayName" + Push "@CPACK_NSIS_DISPLAY_NAME@" + Call ConditionalAddToRegisty + Push "DisplayVersion" + Push "@CPACK_PACKAGE_VERSION@" + Call ConditionalAddToRegisty + Push "Publisher" + Push "@CPACK_PACKAGE_VENDOR@" + Call ConditionalAddToRegisty + Push "UninstallString" + Push "$INSTDIR\@UNINSTALLER_NAME@" + Call ConditionalAddToRegisty + Push "NoRepair" + Push "1" + Call ConditionalAddToRegisty + + !ifdef CPACK_NSIS_ADD_REMOVE + ;Create add/remove functionality + Push "ModifyPath" + Push "$INSTDIR\AddRemove.exe" + Call ConditionalAddToRegisty + !else + Push "NoModify" + Push "1" + Call ConditionalAddToRegisty + !endif + + ; Package the add/remove icon file + File "@ADD_REMOVE_ICON_PATH@" + + ; Optional registration + Push "DisplayIcon" + Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" + Call ConditionalAddToRegisty + Push "HelpLink" + Push "@CPACK_NSIS_HELP_LINK@" + Call ConditionalAddToRegisty + Push "URLInfoAbout" + Push "@CPACK_NSIS_URL_INFO_ABOUT@" + Call ConditionalAddToRegisty + Push "Contact" + Push "@CPACK_NSIS_CONTACT@" + Call ConditionalAddToRegisty + !insertmacro INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State" + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" +@CPACK_NSIS_CREATE_ICONS@ +@CPACK_NSIS_CREATE_ICONS_EXTRA@ + + ; Conditional handling for Interface specific options + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@INTERFACE_SHORTCUT_NAME@.lnk" \ + "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" + + ${If} "@PRODUCTION_BUILD@" == "1" + ; hifi:// protocol handler registry entries + WriteRegStr HKCR '@HIGH_FIDELITY_PROTOCOL@' '' 'URL:High Fidelity Protocol' + WriteRegStr HKCR '@HIGH_FIDELITY_PROTOCOL@' 'URL Protocol' '' + WriteRegStr HKCR '@HIGH_FIDELITY_PROTOCOL@\DefaultIcon' '' '$INSTDIR\@INTERFACE_WIN_EXEC_NAME@,1' + WriteRegStr HKCR '@HIGH_FIDELITY_PROTOCOL@\shell\open\command' '' '$INSTDIR\@INTERFACE_WIN_EXEC_NAME@ --url "%1"' + ${EndIf} + + ${EndIf} + + ; Conditional handling for server console shortcut + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ + "$INSTDIR\@CONSOLE_WIN_EXEC_NAME@" + ${EndIf} + + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@" + + ;Read a value from an InstallOptions INI file + !insertmacro INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" + !insertmacro INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State" + !insertmacro INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State" + + ; Write special uninstall registry entries + Push "StartMenu" + Push "$STARTMENU_FOLDER" + Call ConditionalAddToRegisty + Push "DoNotAddToPath" + Push "$DO_NOT_ADD_TO_PATH" + Call ConditionalAddToRegisty + Push "AddToPathAllUsers" + Push "$ADD_TO_PATH_ALL_USERS" + Call ConditionalAddToRegisty + Push "AddToPathCurrentUser" + Push "$ADD_TO_PATH_CURRENT_USER" + Call ConditionalAddToRegisty + Push "InstallToDesktop" + Push "$INSTALL_DESKTOP" + Call ConditionalAddToRegisty + + !insertmacro MUI_STARTMENU_WRITE_END + +@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + +SectionEnd + +Section "-Add to path" + Push $INSTDIR\bin + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath + StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0 + Call AddToPath + doNotAddToPath: +SectionEnd + +;-------------------------------- +; Create custom pages +Function InstallOptionsPage + !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@" + !insertmacro INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini" + +FunctionEnd + +; Make sure nsDialogs is included before we use it +!include "nsdialogs.nsh" + +Var PostInstallDialog +Var OptionsLabel +Var DesktopClientCheckbox +Var DesktopServerCheckbox +Var ServerStartupCheckbox +Var LaunchNowCheckbox +Var CurrentOffset +Var OffsetUnits +Var CopyFromProductionCheckbox + +!macro SetPostInstallOption Checkbox OptionName Default + ; reads the value for the given post install option to the registry + ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" + + ${If} $0 == "NO" + ; the value in the registry says it should not be checked + ${NSD_SetState} ${Checkbox} ${BST_UNCHECKED} + ${ElseIf} $0 == "YES" + ; the value in the registry says it should be checked + ${NSD_SetState} ${Checkbox} ${BST_CHECKED} + ${Else} + ; the value in the registry was not in the expected format, use default + ${NSD_SetState} ${Checkbox} ${Default} + ${EndIf} +!macroend + +Function PostInstallOptionsPage + ; Set the text on the dialog button to match finish, hide the back and cancel buttons + GetDlgItem $R1 $hwndparent 1 + SendMessage $R1 ${WM_SETTEXT} 0 "STR:&Finish" + + GetDlgItem $R3 $hwndparent 3 + ShowWindow $R3 0 + + nsDialogs::Create 1018 + Pop $PostInstallDialog + + ${If} $PostInstallDialog == error + Abort + ${EndIf} + + ${NSD_CreateLabel} 0 0 100% 12u "Setup Options" + Pop $OptionsLabel + + ; Set label to bold + CreateFont $R2 "Arial" 10 700 + SendMessage $OptionsLabel ${WM_SETFONT} $R2 0 + + ; Force label redraw + ShowWindow $OptionsLabel ${SW_HIDE} + ShowWindow $OptionsLabel ${SW_SHOW} + + StrCpy $CurrentOffset 15 + StrCpy $OffsetUnits u + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@" + Pop $DesktopClientCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} + + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@" + Pop $DesktopServerCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} + + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup" + Pop $ServerStartupCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + + IntOp $CurrentOffset $CurrentOffset + 15 + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console Now" + ${Else} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Now" + ${EndIf} + + Pop $LaunchNowCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $LaunchNowCheckbox @LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + + ${If} @PR_BUILD@ == 1 + ; push the offset + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install" + Pop $CopyFromProductionCheckbox + + ${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED} + ${EndIf} + + nsDialogs::Show +FunctionEnd + +!macro WritePostInstallOption OptionName Option + ; writes the value for the given post install option to the registry + WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option} +!macroend + +Var DesktopClientState +Var DesktopServerState +Var ServerStartupState +Var LaunchNowState +Var CopyFromProductionState + +Function HandlePostInstallOptions + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a desktop shortcut to High Fidelity + ${NSD_GetState} $DesktopClientCheckbox $DesktopClientState + + ${If} $DesktopClientState == ${BST_CHECKED} + CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" + !insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES + ${Else} + !insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO + ${EndIf} + + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ; check if the user asked for a desktop shortcut to Server Console + ${NSD_GetState} $DesktopServerCheckbox $DesktopServerState + + ${If} $DesktopServerState == ${BST_CHECKED} + CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_WIN_EXEC_NAME@" + !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES + ${Else} + !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO + ${EndIf} + + ; check if the user asked to have Server Console launched every startup + ${NSD_GetState} $ServerStartupCheckbox $ServerStartupState + + ${If} $ServerStartupState == ${BST_CHECKED} + CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_WIN_EXEC_NAME@" + + !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES + ${Else} + !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO + ${EndIf} + ${EndIf} + + ${If} @PR_BUILD@ == 1 + ; check if we need to copy settings/content from production for this PR build + ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState + + ${If} $CopyFromProductionState == ${BST_CHECKED} + SetShellVarContext current + + StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@" + + ; we need to copy whatever is in the data folder for production build to the data folder for this build + CreateDirectory $0 + + ClearErrors + + ; copy the data from production build to this PR build + CopyFiles "$APPDATA\High Fidelity\*" $0 + + ; handle an error in copying files + IfErrors 0 NoError + + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \ + "There was a problem copying your production content and settings to $0 for this PR build.$\r$\n$\r$\nPlease copy them manually." + + NoError: + ${EndIf} + ${EndIf} + + ; check if we need to launch an application post-install + ${NSD_GetState} $LaunchNowCheckbox $LaunchNowState + + ${If} $LaunchNowState == ${BST_CHECKED} + !insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES + + ; both launches use the explorer trick in case the user has elevated permissions for the installer + ; it won't be possible to use this approach if either application should be launched with a command line param + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_WIN_EXEC_NAME@"' + ${Else} + Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' + ${EndIf} + ${Else} + !insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO + ${EndIf} + +FunctionEnd + +!include nsProcess.nsh + +!macro PromptForRunningApplication applicationName displayName action prompter + ${nsProcess::FindProcess} ${applicationName} $R0 + + ${If} $R0 == 0 + ; the process is running, ask the user if they want us to close it + MessageBox MB_OK|MB_ICONEXCLAMATION \ + "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it and try again." + Abort + ${EndIf} +!macroend + +!macro CheckForRunningApplications action prompter + !insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "@INTERFACE_SHORTCUT_NAME@" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "@CONSOLE_SHORTCUT_NAME@" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@DS_EXEC_NAME@" "Domain Server" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@AC_EXEC_NAME@" "Assignment Client" ${action} ${prompter} +!macroend + +;-------------------------------- +; determine admin versus local install +Function un.onInit + + ; In order for the uninstaller to be able to remove itself, we have to do some trickery here. + ; If the $EXEPATH does not contain the $TEMP dir, this instance is not the copied one + ; so we move it to the $TEMP dir and then execute the copied uninstaller. + + ${If} $EXEDIR != $TEMP + CopyFiles /SILENT $EXEPATH $TEMP\@UNINSTALLER_NAME@ + ExecWait '"$Temp\@UNINSTALLER_NAME@" _?=$INSTDIR' $0 + SetErrorLevel $0 + Quit + ${EndIf} + + ; make sure none of the installed applications are still running + !insertmacro CheckForRunningApplications "uninstalled" "Uninstaller" + ${nsProcess::Unload} + + ; attempt to elevate the uninstaller to admin status + uac_tryagain: + !insertmacro UAC_RunElevated + ${Switch} $0 + ${Case} 0 + ${IfThen} $1 = 1 ${|} Quit ${|} ;we are the outer process, the inner process has done its work, we are done + ${IfThen} $3 <> 0 ${|} ${Break} ${|} ;we are admin, let the show go on + ${If} $1 = 3 ;RunAs completed successfully, but with a non-admin user + MessageBox mb_YesNo|mb_IconExclamation|mb_TopMost|mb_SetForeground "The uninstaller requires admin privileges, try again" /SD IDNO IDYES uac_tryagain IDNO 0 + ${EndIf} + ;fall-through and die + + ${Case} 1223 + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "The uninstaller requires admin privileges, aborting!" + Quit + ${Case} 1062 + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Logon service not running, aborting!" + Quit + ${Default} + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0" + Quit + ${EndSwitch} + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + Goto done + StrCmp $1 "Power" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + Goto done + + noLM: + ;Get installation folder from registry if available + + done: + +FunctionEnd + +;--- Add/Remove callback functions: --- +!macro SectionList MacroName + ;This macro used to perform operation on multiple sections. + ;List all of your components in following manner here. +@CPACK_NSIS_COMPONENT_SECTION_LIST@ +!macroend + +Section -FinishComponents + ;Removes unselected components and writes component status to registry + !insertmacro SectionList "FinishSection" + +!ifdef CPACK_NSIS_ADD_REMOVE + ; Get the name of the installer executable + System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1' + StrCpy $R3 $R0 + + ; Strip off the last 13 characters, to see if we have AddRemove.exe + StrLen $R1 $R0 + IntOp $R1 $R0 - 13 + StrCpy $R2 $R0 13 $R1 + StrCmp $R2 "AddRemove.exe" addremove_installed + + ; We're not running AddRemove.exe, so install it + CopyFiles $R3 $INSTDIR\AddRemove.exe + + addremove_installed: +!endif +SectionEnd +;--- End of Add/Remove callback functions --- + +;-------------------------------- +; Component dependencies +Function .onSelChange + !insertmacro SectionList MaybeSelectionChanged + + ; if neither component is selected, disable the install button + ${IfNot} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${AndIfNot} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + GetDlgItem $0 $HWNDPARENT 1 + EnableWindow $0 0 + ${Else} + GetDlgItem $0 $HWNDPARENT 1 + EnableWindow $0 1 + ${EndIf} +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +; the normal uninstaller section is only defined for inner (they're not needed in the "outer" +; installer and will just cause warnings because there is no WriteInstaller command) + +!ifdef INNER +Section "Uninstall" + + ReadRegStr $START_MENU SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu" + ;MessageBox MB_OK "Start menu is in: $START_MENU" + ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath" + ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers" + ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser" + ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS" + ReadRegStr $INSTALL_DESKTOP SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop" + ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP " + +@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + + ;Remove files we installed. + ;Keep the list of directories here in sync with the File commands above. +@CPACK_NSIS_DELETE_FILES@ +@CPACK_NSIS_DELETE_DIRECTORIES@ + +!ifdef CPACK_NSIS_ADD_REMOVE + ;Remove the add/remove program + Delete "$INSTDIR\AddRemove.exe" +!endif + + ;Remove the Add/Remove icon + Delete "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" + + ;Remove the uninstaller itself. + Delete "$INSTDIR\@UNINSTALLER_NAME@" + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ;Remove the installation directory if it is empty. + RMDir "$INSTDIR" + + ; Remove the registry entries. + DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ; Removes all optional components + !insertmacro SectionList "RemoveSection_CPack" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" + Delete "$SMPROGRAMS\$MUI_TEMP\@INTERFACE_SHORTCUT_NAME@.lnk" + Delete "$SMPROGRAMS\$MUI_TEMP\@CONSOLE_SHORTCUT_NAME@.lnk" + Delete "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" + Delete "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" + Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" +@CPACK_NSIS_DELETE_ICONS@ +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete High Fidelity protocol handling, if installed + ${If} "@PRODUCTION_BUILD@" == "1" + DeleteRegKey HKCR '@HIGH_FIDELITY_PROTOCOL@' + ${EndIf} + + ;Delete post-install option information from registry + DeleteRegKey HKLM '@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@' + + ;Delete empty start menu parent diretories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + startMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors startMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop + startMenuDeleteLoopDone: + + ; If the user changed the shortcut, then untinstall may not work. This should + ; try to fix it. + StrCpy $MUI_TEMP "$START_MENU" + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent diretories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + secondStartMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors secondStartMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop + secondStartMenuDeleteLoopDone: + + DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + Push $INSTDIR\bin + StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0 + Call un.RemoveFromPath + doNotRemoveFromPath: +SectionEnd +!endif + +;-------------------------------- +; determine admin versus local install +; Is install for "AllUsers" or "JustMe"? +; Default to "JustMe" - set to "AllUsers" if admin or on Win9x +; This function is used for the very first "custom page" of the installer. +; This custom page does not show up visibly, but it executes prior to the +; first visible page and sets up $INSTDIR properly... +; Choose different default installation folder based on SV_ALLUSERS... +; "Program Files" for AllUsers, "My Documents" for JustMe... + +Function .onInit + + !ifdef INNER + ; If INNER is defined, then we aren't supposed to do anything except write out + ; the installer. This is better than processing a command line option as it means + ; this entire code path is not present in the final (real) installer. + + WriteUninstaller "$%TEMP%\@UNINSTALLER_NAME@" + + ; just bail out quickly when running the "inner" installer + Quit + !endif + + ; make sure none of the installed applications are still running + !insertmacro CheckForRunningApplications "installed" "Installer" + ${nsProcess::Unload} + + StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst + + ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" inst + + MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ + "@CPACK_NSIS_PACKAGE_NAME@ is already installed. $\n$\nDo you want to uninstall the old version before installing the new one?" \ + IDYES uninst IDNO inst + Abort + +;Run the uninstaller +uninst: + ClearErrors + StrLen $2 "\Uninstall.exe" + StrCpy $3 $0 -$2 # remove "\Uninstall.exe" from UninstallString to get path + ExecWait '$0 _?=$3' ;Do not copy the uninstaller to a temp file + + IfErrors uninst_failed inst +uninst_failed: + MessageBox MB_OK|MB_ICONSTOP "Uninstall failed." + Abort + + +inst: + ; Reads components status for registry + !insertmacro SectionList "InitSection" + + ; check to see if /D has been used to change + ; the install directory by comparing it to the + ; install directory that is expected to be the + ; default + StrCpy $IS_DEFAULT_INSTALLDIR 0 + StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2 + StrCpy $IS_DEFAULT_INSTALLDIR 1 + + StrCpy $SV_ALLUSERS "JustMe" + ; if default install dir then change the default + ; if it is installed for JustMe + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + StrCmp $1 "Power" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + + noLM: + StrCpy $SV_ALLUSERS "AllUsers" + ;Get installation folder from registry if available + + done: + StrCmp $SV_ALLUSERS "AllUsers" 0 +3 + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage + !insertmacro INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + + noOptionsPage: +FunctionEnd diff --git a/cmake/templates/console-build-info.json.in b/cmake/templates/console-build-info.json.in new file mode 100644 index 0000000000..c1ef010e08 --- /dev/null +++ b/cmake/templates/console-build-info.json.in @@ -0,0 +1,4 @@ +{ + "releaseType": "@RELEASE_TYPE@", + "buildIdentifier": "@BUILD_VERSION@" +} diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 2200916765..b7eb8c0138 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -34,8 +34,10 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) # libcrypto uses dlopen in libdl if (UNIX) target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS}) -endif (UNIX) +endif () -include_application_version() -package_libraries_for_deployment() -consolidate_stack_components() \ No newline at end of file +if (WIN32) + package_libraries_for_deployment() +endif () + +install_beside_console() diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 2332f25dfa..e38aa2a75a 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -65,7 +65,7 @@ { "name": "viewpoint", "label": "Viewpoint", - "placeholder": "/512,512,512" + "placeholder": "/0,0,0" } ] } @@ -308,7 +308,7 @@ "type": "checkbox", "label": "Dynamic Jitter Buffers", "help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing", - "default": false, + "default": true, "advanced": true }, { @@ -386,8 +386,8 @@ "name": "persistFilename", "label": "Entities Filename", "help": "the path to the file entities are stored in. Make sure the path exists.", - "placeholder": "resources/models.json.gz", - "default": "resources/models.json.gz", + "placeholder": "models.json.gz", + "default": "models.json.gz", "advanced": true }, { diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index c733256fe3..7dc94421be 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -126,6 +126,20 @@ var viewHelpers = { } } +var qs = (function(a) { + if (a == "") return {}; + var b = {}; + for (var i = 0; i < a.length; ++i) + { + var p=a[i].split('=', 2); + if (p.length == 1) + b[p[0]] = ""; + else + b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); + } + return b; +})(window.location.search.substr(1).split('&')); + $(document).ready(function(){ /* * Clamped-width. @@ -272,9 +286,80 @@ $(document).ready(function(){ // $('body').scrollspy({ target: '#setup-sidebar'}) - reloadSettings(); + reloadSettings(function(success){ + if (success) { + handleAction(); + } else { + swal({ + title: '', + type: 'error', + text: "There was a problem loading the domain settings.\nPlease refresh the page to try again.", + }); + } + }); }); +function handleAction() { + // check if we were passed an action to handle + var action = qs["action"]; + + if (action == "share") { + // figure out if we already have a stored domain ID + if (Settings.data.values.metaverse.id.length > 0) { + // we need to ask the API what a shareable name for this domain is + getDomainFromAPI(function(data){ + // check if we have owner_places (for a real domain) or a name (for a temporary domain) + if (data && data.status == "success") { + var shareName; + if (data.domain.owner_places) { + shareName = data.domain.owner_places[0].name + } else if (data.domain.name) { + shareName = data.domain.name; + } + + var shareLink = "hifi://" + shareName; + + console.log(shareLink); + + // show a dialog with a copiable share URL + swal({ + title: "Share", + type: "input", + inputPlaceholder: shareLink, + inputValue: shareLink, + text: "Copy this URL to invite friends to your domain.", + closeOnConfirm: true + }); + + $('.sweet-alert input').select(); + + } else { + // show an error alert + swal({ + title: '', + type: 'error', + text: "There was a problem retreiving domain information from High Fidelity API.", + confirmButtonText: 'Try again', + showCancelButton: true, + closeOnConfirm: false + }, function(isConfirm){ + if (isConfirm) { + // they want to try getting domain share info again + showSpinnerAlert("Requesting domain information...") + handleAction(); + } else { + swal.close(); + } + }); + } + }); + } else { + // no domain ID present, just show the share dialog + createTemporaryDomain(); + } + } +} + function dynamicButton(button_id, text) { return $(""); } @@ -577,29 +662,33 @@ function placeTableRowForPlaceObject(place) { return placeTableRow(place.name, placePathOrIndex, false); } -function reloadPlacesOrTemporaryName() { +function getDomainFromAPI(callback) { // we only need to do this if we have a current domain ID var domainID = Settings.data.values.metaverse.id; if (domainID.length > 0) { var domainURL = Settings.METAVERSE_URL + "/api/v1/domains/" + domainID; - $.getJSON(domainURL, function(data){ - // check if we have owner_places (for a real domain) or a name (for a temporary domain) - if (data.status == "success") { - if (data.domain.owner_places) { - // add a table row for each of these names - _.each(data.domain.owner_places, function(place){ - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); - }); - } else if (data.domain.name) { - // add a table row for this temporary domain name - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); - } - } - }); + $.getJSON(domainURL, callback).fail(callback); } } +function reloadPlacesOrTemporaryName() { + getDomainFromAPI(function(data){ + // check if we have owner_places (for a real domain) or a name (for a temporary domain) + if (data.status == "success") { + if (data.domain.owner_places) { + // add a table row for each of these names + _.each(data.domain.owner_places, function(place){ + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); + }); + } else if (data.domain.name) { + // add a table row for this temporary domain name + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); + } + } + }) +} + function appendDomainIDButtons() { var domainIDInput = $(Settings.DOMAIN_ID_SELECTOR); @@ -728,7 +817,7 @@ function createTemporaryDomain() { }); } -function reloadSettings() { +function reloadSettings(callback) { $.getJSON('/settings.json', function(data){ _.extend(data, viewHelpers) @@ -757,6 +846,12 @@ function reloadSettings() { placement: 'right', title: 'This setting is in the master config file and cannot be changed' }); + + // call the callback now that settings are loaded + callback(true); + }).fail(function() { + // call the failure object since settings load faild + callback(false) }); } diff --git a/domain-server/resources/web/settings/js/sweetalert.min.js b/domain-server/resources/web/settings/js/sweetalert.min.js index d81d1e906b..5c029c2532 100755 --- a/domain-server/resources/web/settings/js/sweetalert.min.js +++ b/domain-server/resources/web/settings/js/sweetalert.min.js @@ -1 +1 @@ -!function(e,t,n){"use strict";!function o(e,t,n){function a(s,l){if(!t[s]){if(!e[s]){var i="function"==typeof require&&require;if(!l&&i)return i(s,!0);if(r)return r(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=t[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return a(n?n:t)},c,c.exports,o,e,t,n)}return t[s].exports}for(var r="function"==typeof require&&require,s=0;s=0;)n=n.replace(" "+t+" "," ");e.className=n.replace(/^\s+|\s+$/g,"")}},i=function(e){var n=t.createElement("div");return n.appendChild(t.createTextNode(e)),n.innerHTML},u=function(e){e.style.opacity="",e.style.display="block"},c=function(e){if(e&&!e.length)return u(e);for(var t=0;t0?setTimeout(o,t):e.style.display="none"});o()},h=function(n){if("function"==typeof MouseEvent){var o=new MouseEvent("click",{view:e,bubbles:!1,cancelable:!0});n.dispatchEvent(o)}else if(t.createEvent){var a=t.createEvent("MouseEvents");a.initEvent("click",!1,!1),n.dispatchEvent(a)}else t.createEventObject?n.fireEvent("onclick"):"function"==typeof n.onclick&&n.onclick()},g=function(t){"function"==typeof t.stopPropagation?(t.stopPropagation(),t.preventDefault()):e.event&&e.event.hasOwnProperty("cancelBubble")&&(e.event.cancelBubble=!0)};a.hasClass=r,a.addClass=s,a.removeClass=l,a.escapeHtml=i,a._show=u,a.show=c,a._hide=d,a.hide=f,a.isDescendant=p,a.getTopMargin=m,a.fadeIn=v,a.fadeOut=y,a.fireClick=h,a.stopEventPropagation=g},{}],5:[function(t,o,a){Object.defineProperty(a,"__esModule",{value:!0});var r=t("./handle-dom"),s=t("./handle-swal-dom"),l=function(t,o,a){var l=t||e.event,i=l.keyCode||l.which,u=a.querySelector("button.confirm"),c=a.querySelector("button.cancel"),d=a.querySelectorAll("button[tabindex]");if(-1!==[9,13,32,27].indexOf(i)){for(var f=l.target||l.srcElement,p=-1,m=0;m"),i.innerHTML=e.html?e.text:s.escapeHtml(e.text||"").split("\n").join("
"),e.text&&s.show(i),e.customClass)s.addClass(t,e.customClass),t.setAttribute("data-custom-class",e.customClass);else{var d=t.getAttribute("data-custom-class");s.removeClass(t,d),t.setAttribute("data-custom-class","")}if(s.hide(t.querySelectorAll(".sa-icon")),e.type&&!a.isIE8()){var f=function(){for(var o=!1,a=0;ao;o++)n=parseInt(e.substr(2*o,2),16),n=Math.round(Math.min(Math.max(0,n+n*t),255)).toString(16),a+=("00"+n).substr(n.length);return a};o.extend=a,o.hexToRgb=r,o.isIE8=s,o.logStr=l,o.colorLuminance=i},{}]},{},[1])}(window,document); \ No newline at end of file +!function(e,t,n){"use strict";!function o(e,t,n){function a(s,l){if(!t[s]){if(!e[s]){var i="function"==typeof require&&require;if(!l&&i)return i(s,!0);if(r)return r(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=t[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return a(n?n:t)},c,c.exports,o,e,t,n)}return t[s].exports}for(var r="function"==typeof require&&require,s=0;s=0;)n=n.replace(" "+t+" "," ");e.className=n.replace(/^\s+|\s+$/g,"")}},i=function(e){var n=t.createElement("div");return n.appendChild(t.createTextNode(e)),n.innerHTML},u=function(e){e.style.opacity="",e.style.display="block"},c=function(e){if(e&&!e.length)return u(e);for(var t=0;t0?setTimeout(o,t):e.style.display="none"});o()},h=function(n){if("function"==typeof MouseEvent){var o=new MouseEvent("click",{view:e,bubbles:!1,cancelable:!0});n.dispatchEvent(o)}else if(t.createEvent){var a=t.createEvent("MouseEvents");a.initEvent("click",!1,!1),n.dispatchEvent(a)}else t.createEventObject?n.fireEvent("onclick"):"function"==typeof n.onclick&&n.onclick()},b=function(t){"function"==typeof t.stopPropagation?(t.stopPropagation(),t.preventDefault()):e.event&&e.event.hasOwnProperty("cancelBubble")&&(e.event.cancelBubble=!0)};a.hasClass=r,a.addClass=s,a.removeClass=l,a.escapeHtml=i,a._show=u,a.show=c,a._hide=d,a.hide=f,a.isDescendant=p,a.getTopMargin=m,a.fadeIn=v,a.fadeOut=y,a.fireClick=h,a.stopEventPropagation=b},{}],5:[function(t,o,a){Object.defineProperty(a,"__esModule",{value:!0});var r=t("./handle-dom"),s=t("./handle-swal-dom"),l=function(t,o,a){var l=t||e.event,i=l.keyCode||l.which,u=a.querySelector("button.confirm"),c=a.querySelector("button.cancel"),d=a.querySelectorAll("button[tabindex]");if(-1!==[9,13,32,27].indexOf(i)){for(var f=l.target||l.srcElement,p=-1,m=0;m"),i.innerHTML=e.html?e.text:s.escapeHtml(e.text||"").split("\n").join("
"),e.text&&s.show(i),e.customClass)s.addClass(t,e.customClass),t.setAttribute("data-custom-class",e.customClass);else{var d=t.getAttribute("data-custom-class");s.removeClass(t,d),t.setAttribute("data-custom-class","")}if(s.hide(t.querySelectorAll(".sa-icon")),e.type&&!a.isIE8()){var f=function(){for(var o=!1,a=0;ao;o++)n=parseInt(e.substr(2*o,2),16),n=Math.round(Math.min(Math.max(0,n+n*t),255)).toString(16),a+=("00"+n).substr(n.length);return a};o.extend=a,o.hexToRgb=r,o.isIE8=s,o.logStr=l,o.colorLuminance=i},{}]},{},[1]),"function"==typeof define&&define.amd?define(function(){return sweetAlert}):"undefined"!=typeof module&&module.exports&&(module.exports=sweetAlert)}(window,document); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index e219b47571..19950c9e0b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include #include @@ -35,6 +35,7 @@ #include #include #include +#include #include "DomainServerNodeData.h" #include "NodeConnectionData.h" @@ -46,7 +47,7 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _gatekeeper(this), - _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), + _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _httpsManager(NULL), _allAssignments(), _unfulfilledAssignments(), @@ -64,24 +65,22 @@ DomainServer::DomainServer(int argc, char* argv[]) : LogUtils::init(); Setting::init(); - - // to work around the Qt constant wireless scanning, set the env for polling interval very high - const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); - qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); - setOrganizationName("High Fidelity"); + setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); - setApplicationVersion(BUILD_VERSION); + setApplicationVersion(BuildInfo::VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) AccountManager::getInstance(true); - _settingsManager.setupConfigMap(arguments()); + auto args = arguments(); + + _settingsManager.setupConfigMap(args); // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32 @@ -110,6 +109,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : // preload some user public keys so they can connect on first request _gatekeeper.preloadAllowedUserPublicKeys(); + + optionallyGetTemporaryName(args); } } @@ -157,7 +158,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { QSslCertificate sslCertificate(&certFile); QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); - _httpsManager = new HTTPSManager(DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this); + _httpsManager = new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this); qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT; @@ -209,6 +210,80 @@ bool DomainServer::optionallySetupOAuth() { return true; } +static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; + +void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { + // check for the temporary name parameter + const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; + + if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) { + + // make sure we don't already have a domain ID + const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); + if (idValueVariant) { + qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings." + << "Will not request temporary name."; + return; + } + + // we've been asked to grab a temporary name from the API + // so fire off that request now + auto& accountManager = AccountManager::getInstance(); + + // ask our auth endpoint for our balance + JSONCallbackParameters callbackParameters; + callbackParameters.jsonCallbackReceiver = this; + callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess"; + callbackParameters.errorCallbackReceiver = this; + callbackParameters.errorCallbackMethod = "handleTempDomainError"; + + accountManager.sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParameters); + } +} + +void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + + // grab the information for the new domain + static const QString DATA_KEY = "data"; + static const QString DOMAIN_KEY = "domain"; + static const QString ID_KEY = "id"; + static const QString NAME_KEY = "name"; + + auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); + if (!domainObject.isEmpty()) { + auto id = domainObject[ID_KEY].toString(); + auto name = domainObject[NAME_KEY].toString(); + + qInfo() << "Received new temporary domain name" << name; + qDebug() << "The temporary domain ID is" << id; + + // store the new domain ID and auto network setting immediately + QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id); + auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); + _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object()); + + // store the new ID and auto networking setting on disk + _settingsManager.persistToFile(); + + // change our domain ID immediately + DependencyManager::get()->setSessionUUID(QUuid { id }); + + // change our automatic networking settings so that we're communicating with the ICE server + setupICEHeartbeatForFullNetworking(); + + } else { + qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again" + << "via domain-server relaunch or from the domain-server settings."; + } +} + +void DomainServer::handleTempDomainError(QNetworkReply& requestReply) { + qWarning() << "A temporary name was requested but there was an error creating one. Please try again via domain-server relaunch" + << "or from the domain-server settings."; +} + const QString DOMAIN_CONFIG_ID_KEY = "id"; const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; @@ -259,7 +334,6 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // set our LimitedNodeList UUID to match the UUID from our config // nodes will currently use this to add resources to data-web that relate to our domain - const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { nodeList->setSessionUUID(idValueVariant->toString()); @@ -372,19 +446,7 @@ void DomainServer::setupAutomaticNetworking() { _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { - // call our sendHeartbeatToIceServer immediately anytime a local or public socket changes - connect(nodeList.data(), &LimitedNodeList::localSockAddrChanged, - this, &DomainServer::sendHeartbeatToIceServer); - connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, - this, &DomainServer::sendHeartbeatToIceServer); - - // we need this DS to know what our public IP is - start trying to figure that out now - nodeList->startSTUNPublicSocketUpdate(); - - // setup a timer to heartbeat with the ice-server every so often - QTimer* iceHeartbeatTimer = new QTimer(this); - connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); - iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); + setupICEHeartbeatForFullNetworking(); } if (!didSetupAccountManagerWithAccessToken()) { @@ -434,6 +496,26 @@ void DomainServer::setupAutomaticNetworking() { dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); } +void DomainServer::setupICEHeartbeatForFullNetworking() { + auto limitedNodeList = DependencyManager::get(); + + // call our sendHeartbeatToIceServer immediately anytime a local or public socket changes + connect(limitedNodeList.data(), &LimitedNodeList::localSockAddrChanged, + this, &DomainServer::sendHeartbeatToIceServer); + connect(limitedNodeList.data(), &LimitedNodeList::publicSockAddrChanged, + this, &DomainServer::sendHeartbeatToIceServer); + + // we need this DS to know what our public IP is - start trying to figure that out now + limitedNodeList->startSTUNPublicSocketUpdate(); + + if (!_iceHeartbeatTimer) { + // setup a timer to heartbeat with the ice-server every so often + _iceHeartbeatTimer = new QTimer { this }; + connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); + _iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); + } +} + void DomainServer::loginFailed() { qDebug() << "Login to data server has failed. domain-server will now quit"; QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); @@ -1068,7 +1150,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; QString pathForAssignmentScript(const QUuid& assignmentUUID) { - QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); + QString newPath { ServerPathUtils::getDataDirectory() + "/" + QString(ASSIGNMENT_SCRIPT_HOST_LOCATION) }; newPath += "/scripts/"; // append the UUID for this script as the new filename, remove the curly braces newPath += uuidStringWithoutCurlyBraces(assignmentUUID); @@ -1786,16 +1868,25 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag const QString PATHS_SETTINGS_KEYPATH_FORMAT = "%1.%2"; const QString PATH_VIEWPOINT_KEY = "viewpoint"; + const QString INDEX_PATH = "/"; // check out paths in the _configMap to see if we have a match - const QVariant* pathMatch = valueForKeyPath(_settingsManager.getSettingsMap(), - QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY) - .arg(pathQuery)); - if (pathMatch) { + auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery); + const QVariant* pathMatch = valueForKeyPath(_settingsManager.getSettingsMap(), keypath); + + if (pathMatch || pathQuery == INDEX_PATH) { // we got a match, respond with the resulting viewpoint auto nodeList = DependencyManager::get(); - QString responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString(); + QString responseViewpoint; + + // if we didn't match the path BUT this is for the index path then send back our default + if (pathMatch) { + responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString(); + } else { + const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1"; + responseViewpoint = DEFAULT_INDEX_PATH; + } if (!responseViewpoint.isEmpty()) { QByteArray viewpointUTF8 = responseViewpoint.toUtf8(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index f5d5ff8f40..a4892b446b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -74,6 +74,9 @@ private slots: void sendHeartbeatToIceServer(); void handleConnectedNode(SharedNodePointer newNode); + + void handleTempDomainSuccess(QNetworkReply& requestReply); + void handleTempDomainError(QNetworkReply& requestReply); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); @@ -81,10 +84,13 @@ private: bool optionallyReadX509KeyAndCertificate(); bool optionallySetupAssignmentPayment(); + void optionallyGetTemporaryName(const QStringList& arguments); + bool didSetupAccountManagerWithAccessToken(); bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); + void setupICEHeartbeatForFullNetworking(); void sendHeartbeatToDataServer(const QString& networkAddress); unsigned int countConnectedUsers(); @@ -144,6 +150,8 @@ private: DomainServerSettingsManager _settingsManager; HifiSockAddr _iceServerSocket; + + QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer friend class DomainGatekeeper; }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 888df219dd..e10007784f 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -193,7 +193,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection qDebug() << "DomainServerSettingsManager postedObject -" << postedObject; // we recurse one level deep below each group for the appropriate setting - recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig()); + recurseJSONObjectAndOverwriteSettings(postedObject); // store whatever the current _settingsMap is to file persistToFile(); @@ -407,8 +407,9 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson return QJsonObject(); } -void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, - QVariantMap& settingsVariant) { +void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { + auto& settingsVariant = _configMap.getUserConfig(); + // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { QJsonValue rootValue = postedObject[rootKey]; @@ -481,6 +482,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ settingsVariant.remove(rootKey); } } + + // re-merge the user and master configs after a settings change + _configMap.mergeMasterAndUserConfigs(); } void DomainServerSettingsManager::persistToFile() { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 7f71a89da5..d6dd5070a9 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -46,7 +46,7 @@ private slots: private: QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); - void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant); + void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); @@ -56,6 +56,8 @@ private: double _descriptionVersion; QJsonArray _descriptionArray; HifiConfigVariantMap _configMap; + + friend class DomainServer; }; #endif // hifi_DomainServerSettingsManager_h diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 790cc07c56..b3b5c8b9be 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -23,6 +23,8 @@ #include "DomainServer.h" int main(int argc, char* argv[]) { + disableQtBearerPoll(); // Fixes wifi ping spikes + #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 3b46a51960..431322d8c2 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -108,7 +108,7 @@ var GRABBABLE_PROPERTIES = [ "position", "rotation", "gravity", - "collisionMask", + "collidesWith", "collisionsWillMove", "locked", "name", @@ -134,12 +134,11 @@ var blacklist = []; //we've created various ways of visualizing looking for and moving distant objects var USE_ENTITY_LINES_FOR_SEARCHING = false; var USE_OVERLAY_LINES_FOR_SEARCHING = true; -var USE_PARTICLE_BEAM_FOR_SEARCHING = false; var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = true; -var TEMPORARY_PARTICLE_BEAM_LIFETIME = 30; + var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; @@ -163,9 +162,9 @@ var STATE_CONTINUE_EQUIP = 14; var STATE_WAITING_FOR_BUMPER_RELEASE = 15; var STATE_EQUIP_SPRING = 16; -// collision masks are specified by comma-separated list of group names -// the possible list of names is: static, dynamic, kinematic, myAvatar, otherAvatar -var COLLISION_MASK_WHILE_GRABBED = "dynamic,otherAvatar"; +// "collidesWith" is specified by comma-separated list of group names +// the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar +var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar"; function stateToName(state) { switch (state) { @@ -475,24 +474,6 @@ function MyController(hand) { } }; - this.handleParticleBeam = function(position, orientation, color) { - - var rotation = Quat.angleAxis(0, { - x: 1, - y: 0, - z: 0 - }); - - var finalRotation = Quat.multiply(orientation, rotation); - var lifespan = LINE_LENGTH / 10; - var speed = 5; - var spread = 2; - if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } - }; this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { @@ -505,7 +486,6 @@ function MyController(hand) { var lifespan = distance / speed; - if (this.particleBeam === null) { this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } else { @@ -520,6 +500,7 @@ function MyController(hand) { isEmitting: true, position: position, visible: false, + lifetime: 60, "name": "Particle Beam", "color": color, "maxParticles": 2000, @@ -555,15 +536,6 @@ function MyController(hand) { }, "particleRadius": 0.015, "radiusSpread": 0.005, - // "radiusStart": 0.01, - // "radiusFinish": 0.01, - // "colorSpread": { - // "red": 0, - // "green": 0, - // "blue": 0 - // }, - // "colorStart": color, - // "colorFinish": color, "alpha": 1, "alphaSpread": 0, "alphaStart": 1, @@ -712,9 +684,8 @@ function MyController(hand) { this.particleBeamOff = function() { if (this.particleBeam !== null) { - Entities.editEntity(this.particleBeam, { - visible: false - }) + Entities.deleteEntity(this.particleBeam); + this.particleBeam = null; } } @@ -744,7 +715,7 @@ function MyController(hand) { this.overlayLineOff(); } - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + if (USE_PARTICLE_BEAM_FOR_MOVING === true) { this.particleBeamOff(); } this.searchSphereOff(); @@ -814,8 +785,7 @@ function MyController(hand) { var distantPickRay = { origin: PICK_WITH_HAND_RAY ? handPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(this.getHandRotation()) : - Vec3.mix(Quat.getUp(this.getHandRotation()), Quat.getFront(Camera.orientation), HAND_HEAD_MIX_RATIO), + direction: PICK_WITH_HAND_RAY ? Quat.getUp(this.getHandRotation()) : Vec3.mix(Quat.getUp(this.getHandRotation()), Quat.getFront(Camera.orientation), HAND_HEAD_MIX_RATIO), length: PICK_MAX_DISTANCE }; @@ -853,7 +823,7 @@ function MyController(hand) { intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); } else { intersection = Entities.findRayIntersection(pickRayBacked, true); - } + } if (intersection.intersects) { rayPickedCandidateEntities.push(intersection.entityID); @@ -925,13 +895,14 @@ function MyController(hand) { if (typeof grabbableData.spatialKey === 'undefined') { // We want to give a temporary position offset to this object so it is pulled close to hand var intersectionPointToCenterDistance = Vec3.length(Vec3.subtract(intersection.intersection, - intersection.properties.position)); + intersection.properties.position)); this.temporaryPositionOffset = Vec3.normalize(Vec3.subtract(intersection.properties.position, handPosition)); this.temporaryPositionOffset = Vec3.multiply(this.temporaryPositionOffset, - intersectionPointToCenterDistance * - FAR_TO_NEAR_GRAB_PADDING_FACTOR); + intersectionPointToCenterDistance * + FAR_TO_NEAR_GRAB_PADDING_FACTOR); } this.setState(this.state == STATE_SEARCHING ? STATE_DISTANCE_HOLDING : STATE_EQUIP); + this.searchSphereOff(); return; } @@ -945,10 +916,6 @@ function MyController(hand) { this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); - } - var SEARCH_SPHERE_SIZE = 0.011; var SEARCH_SPHERE_FOLLOW_RATE = 0.50; @@ -956,7 +923,7 @@ function MyController(hand) { // If we hit something with our pick ray, move the search sphere toward that distance this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); } - + var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); if ((USE_OVERLAY_LINES_FOR_SEARCHING === true) && PICK_WITH_HAND_RAY) { @@ -1159,7 +1126,6 @@ function MyController(hand) { } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { this.handleDistantParticleBeam(handPosition, grabbedProperties.position, INTERSECT_COLOR) - // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1597,8 +1563,8 @@ function MyController(hand) { if (this.actionID !== null) { //sometimes we want things to stay right where they are when we let go. var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, - this.grabbedEntity, - DEFAULT_GRABBABLE_DATA); + this.grabbedEntity, + DEFAULT_GRABBABLE_DATA); if (releaseVelocityData.disableReleaseVelocity === true) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -1654,7 +1620,7 @@ function MyController(hand) { // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done if (data["refCount"] == 1) { data["gravity"] = grabbedProperties.gravity; - data["collisionMask"] = grabbedProperties.collisionMask; + data["collidesWith"] = grabbedProperties.collidesWith; data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; data["parentID"] = grabbedProperties.parentID; data["parentJointIndex"] = grabbedProperties.parentJointIndex; @@ -1664,7 +1630,10 @@ function MyController(hand) { y: 0, z: 0 }, - "collisionMask": COLLISION_MASK_WHILE_GRABBED + // bummer, it isn't easy to do bitwise collisionMask operations like this: + //"collisionMask": COLLISION_MASK_WHILE_GRABBED | grabbedProperties.collisionMask + // when using string values + "collidesWith": COLLIDES_WITH_WHILE_GRABBED }; Entities.editEntity(entityID, whileHeldProperties); } @@ -1680,7 +1649,7 @@ function MyController(hand) { if (data["refCount"] < 1) { Entities.editEntity(entityID, { gravity: data["gravity"], - collisionMask: data["collisionMask"], + collidesWith: data["collidesWith"], collisionsWillMove: data["collisionsWillMove"], ignoreForCollisions: data["ignoreForCollisions"], parentID: data["parentID"], @@ -1698,12 +1667,6 @@ function MyController(hand) { var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); -//preload the particle beams so that they are full length when you start searching -if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - rightController.createParticleBeam(); - leftController.createParticleBeam(); -} - var MAPPING_NAME = "com.highfidelity.handControllerGrab"; var mapping = Controller.newMapping(MAPPING_NAME); @@ -1780,31 +1743,7 @@ function cleanup() { rightController.cleanup(); leftController.cleanup(); Controller.disableMapping(MAPPING_NAME); - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - Script.update.disconnect(renewParticleBeamLifetimes); - } + } Script.scriptEnding.connect(cleanup); -Script.update.connect(update); - -// particle systems can end up hanging around if a user crashes or something else causes controller cleanup not to get called. -// we can't create the search system on-demand since it takes some time for the particles to reach their entire length. -// thus the system cannot have a fixed lifetime. this loop updates the lifetimes and will stop updating if a user crashes. - -if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - Script.update.connect(renewParticleBeamLifetimes) -} - -var sinceLastParticleLifetimeUpdate = 0; - -function renewParticleBeamLifetimes(deltaTime) { - //debounce this call since we don't want it 60x a second - sinceLastParticleLifetimeUpdate = sinceLastParticleLifetimeUpdate + deltaTime; - if (sinceLastParticleLifetimeUpdate > TEMPORARY_PARTICLE_BEAM_LIFETIME - 2) { - sinceLastParticleLifetimeUpdate = 0; - } else { - return; - } - rightController.renewParticleBeamLifetime(); - leftController.renewParticleBeamLifetime(); -} +Script.update.connect(update); \ No newline at end of file diff --git a/examples/edit.js b/examples/edit.js index 41f25cb2e5..6c24d00449 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1659,29 +1659,6 @@ PropertiesTool = function(opts) { }); } } - } else if (data.action == "centerAtmosphereToZone") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (var i = 0; i < selectionManager.selections.length; i++) { - var properties = selectionManager.savedProperties[selectionManager.selections[i]]; - if (properties.type == "Zone") { - var centerOfZone = properties.boundingBox.center; - var atmosphereCenter = { - x: centerOfZone.x, - y: centerOfZone.y - properties.atmosphere.innerRadius, - z: centerOfZone.z - }; - - Entities.editEntity(selectionManager.selections[i], { - atmosphere: { - center: atmosphereCenter - }, - }); - } - } - pushCommandForSelections(); - selectionManager._update(); - } } } }); diff --git a/examples/example/entities/changingAtmosphereExample.js b/examples/example/entities/changingAtmosphereExample.js deleted file mode 100644 index 02103fc0fb..0000000000 --- a/examples/example/entities/changingAtmosphereExample.js +++ /dev/null @@ -1,71 +0,0 @@ -// -// 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 } - }, - - }); -}); - diff --git a/examples/example/entities/zoneAtmosphereExample.js b/examples/example/entities/zoneAtmosphereExample.js deleted file mode 100644 index dfebf09f2a..0000000000 --- a/examples/example/entities/zoneAtmosphereExample.js +++ /dev/null @@ -1,65 +0,0 @@ -// -// 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 - }); -}); - diff --git a/examples/example/messagesExample.js b/examples/example/messages/messagesExample.js similarity index 100% rename from examples/example/messagesExample.js rename to examples/example/messages/messagesExample.js diff --git a/examples/example/messagesReceiverExample.js b/examples/example/messages/messagesReceiverExample.js similarity index 100% rename from examples/example/messagesReceiverExample.js rename to examples/example/messages/messagesReceiverExample.js diff --git a/examples/example/messages/messagesTestReceive.js b/examples/example/messages/messagesTestReceive.js new file mode 100644 index 0000000000..3081a8be8e --- /dev/null +++ b/examples/example/messages/messagesTestReceive.js @@ -0,0 +1,16 @@ +var messagesReceivedCount = 0; + +function handleMessages(channel, message, sender) { + print('GOT MESSAGE') + if (sender === MyAvatar.sessionUUID) { + if (channel === 'messageTest') { + messagesReceivedCount++; + print('sendIndex/receiveCount::' + message + "/" + messagesReceivedCount); + } + + } +} + +Messages.messageReceived.connect(handleMessages); +Messages.subscribe('messageTest') +print('READY TO RECEIVE') \ No newline at end of file diff --git a/examples/example/messages/messagesTestSend.js b/examples/example/messages/messagesTestSend.js new file mode 100644 index 0000000000..4bf50b4a05 --- /dev/null +++ b/examples/example/messages/messagesTestSend.js @@ -0,0 +1,18 @@ +var messageIndex = 1; + +var messageRate = 500; +function sendMessage(){ + print('SENDING MESSAGE') +Messages.sendMessage('messageTest',messageIndex) +messageIndex++; +} + +function cleanup(){ + Script.clearInterval(messageInterval); +} + +var messageInterval = Script.setInterval(function(){ + sendMessage(); +},messageRate); + +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/flowArts/lightSaber/lightSaberEntityScript.js b/examples/flowArts/lightSaber/lightSaberEntityScript.js index a86f471449..a23abcc695 100644 --- a/examples/flowArts/lightSaber/lightSaberEntityScript.js +++ b/examples/flowArts/lightSaber/lightSaberEntityScript.js @@ -105,7 +105,8 @@ alphaStart: 0.5, alphaFinish: 0.5, textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", - emitterShouldTrail: false + emitterShouldTrail: false, + lifetime: 1000 } this.beam = Entities.addEntity(props); diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 9ced830e0c..898f2bea6d 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -360,20 +360,6 @@ var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue"); var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url"); - var elZoneAtmosphereCenterX = document.getElementById("property-zone-atmosphere-center-x"); - var elZoneAtmosphereCenterY = document.getElementById("property-zone-atmosphere-center-y"); - var elZoneAtmosphereCenterZ = document.getElementById("property-zone-atmosphere-center-z"); - var elCenterAtmosphereToZone = document.getElementById("center-atmosphere-in-zone"); - - var elZoneAtmosphereInnerRadius = document.getElementById("property-zone-atmosphere-inner-radius"); - var elZoneAtmosphereOuterRadius = document.getElementById("property-zone-atmosphere-outer-radius"); - var elZoneAtmosphereMieScattering = document.getElementById("property-zone-atmosphere-mie-scattering"); - var elZoneAtmosphereRayleighScattering = document.getElementById("property-zone-atmosphere-rayleigh-scattering"); - var elZoneAtmosphereScatteringWavelengthsX = document.getElementById("property-zone-atmosphere-scattering-wavelengths-x"); - var elZoneAtmosphereScatteringWavelengthsY = document.getElementById("property-zone-atmosphere-scattering-wavelengths-y"); - var elZoneAtmosphereScatteringWavelengthsZ = document.getElementById("property-zone-atmosphere-scattering-wavelengths-z"); - var elZoneAtmosphereHasStars = document.getElementById("property-zone-atmosphere-has-stars"); - var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); allSections.push(elPolyVoxSections); var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x"); @@ -606,20 +592,7 @@ elZoneSkyboxColorBlue.value = properties.skybox.color.blue; elZoneSkyboxURL.value = properties.skybox.url; - elZoneAtmosphereCenterX.value = properties.atmosphere.center.x; - elZoneAtmosphereCenterY.value = properties.atmosphere.center.y; - elZoneAtmosphereCenterZ.value = properties.atmosphere.center.z; - elZoneAtmosphereInnerRadius.value = properties.atmosphere.innerRadius; - elZoneAtmosphereOuterRadius.value = properties.atmosphere.outerRadius; - elZoneAtmosphereMieScattering.value = properties.atmosphere.mieScattering; - elZoneAtmosphereRayleighScattering.value = properties.atmosphere.rayleighScattering; - elZoneAtmosphereScatteringWavelengthsX.value = properties.atmosphere.scatteringWavelengths.x; - elZoneAtmosphereScatteringWavelengthsY.value = properties.atmosphere.scatteringWavelengths.y; - elZoneAtmosphereScatteringWavelengthsZ.value = properties.atmosphere.scatteringWavelengths.z; - elZoneAtmosphereHasStars.checked = properties.atmosphere.hasStars; - showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); - showElements(document.getElementsByClassName('atmosphere-section'), elZoneBackgroundMode.value == 'atmosphere'); } else if (properties.type == "ParticleEffect") { for (var i = 0; i < elParticleSections.length; i++) { elParticleSections[i].style.display = 'block'; @@ -874,25 +847,6 @@ elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox','url')); - var zoneAtmosphereCenterChangeFunction = createEmitGroupVec3PropertyUpdateFunction( - 'atmosphere','center', elZoneAtmosphereCenterX, elZoneAtmosphereCenterY, elZoneAtmosphereCenterZ); - elZoneAtmosphereCenterX.addEventListener('change', zoneAtmosphereCenterChangeFunction); - elZoneAtmosphereCenterY.addEventListener('change', zoneAtmosphereCenterChangeFunction); - elZoneAtmosphereCenterZ.addEventListener('change', zoneAtmosphereCenterChangeFunction); - - - elZoneAtmosphereInnerRadius.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','innerRadius')); - elZoneAtmosphereOuterRadius.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','outerRadius')); - elZoneAtmosphereMieScattering.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','mieScattering')); - elZoneAtmosphereRayleighScattering.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','rayleighScattering')); - var zoneAtmosphereScatterWavelengthsChangeFunction = createEmitGroupVec3PropertyUpdateFunction( - 'atmosphere','scatteringWavelengths', elZoneAtmosphereScatteringWavelengthsX, - elZoneAtmosphereScatteringWavelengthsY, elZoneAtmosphereScatteringWavelengthsZ); - elZoneAtmosphereScatteringWavelengthsX.addEventListener('change', zoneAtmosphereScatterWavelengthsChangeFunction); - elZoneAtmosphereScatteringWavelengthsY.addEventListener('change', zoneAtmosphereScatterWavelengthsChangeFunction); - elZoneAtmosphereScatteringWavelengthsZ.addEventListener('change', zoneAtmosphereScatterWavelengthsChangeFunction); - elZoneAtmosphereHasStars.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('atmosphere','hasStars')); - var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); @@ -934,12 +888,6 @@ action: "reloadScript" })); }); - elCenterAtmosphereToZone.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "centerAtmosphereToZone", - })); - }); elPreviewCameraButton.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", @@ -1601,7 +1549,6 @@ @@ -1626,61 +1573,6 @@ - - -
- -
- -
-
Atmosphere Center
-
-
X
-
Y
-
Z
-
- -
-
-
-
-
Atmosphere Inner Radius
-
- -
-
-
-
Atmosphere Outer Radius
-
- -
-
-
-
Atmosphere Mie Scattering
-
- -
-
-
-
Atmosphere Rayleigh Scattering
-
- -
-
-
-
Atmosphere Scattering Wavelenghts
-
-
X
-
Y
-
Z
-
-
- diff --git a/examples/tests/sphereLODTest.js b/examples/tests/sphereLODTest.js new file mode 100644 index 0000000000..dc19094664 --- /dev/null +++ b/examples/tests/sphereLODTest.js @@ -0,0 +1,66 @@ +// +// sphereLodTest.js +// examples/tests +// +// Created by Eric Levin on 1/21/16. +// Copyright 2016 High Fidelity, Inc. + +// A test script for testing LODing of sphere entities and sphere overlays +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); +orientation = Quat.safeEulerAngles(MyAvatar.orientation); +orientation.x = 0; +orientation = Quat.fromVec3Degrees(orientation); +var tablePosition = Vec3.sum(MyAvatar.position, Quat.getFront(orientation)); +tablePosition.y += 0.5; + + +var tableDimensions = { + x: 1, + y: 0.2, + z: 1 +}; +var table = Entities.addEntity({ + type: "Box", + position: tablePosition, + dimensions: tableDimensions, + color: { + red: 70, + green: 21, + blue: 21 + } +}); + + +var sphereDimensions = {x: 0.01, y: 0.01, z: 0.01}; +var entitySpherePosition = Vec3.sum(tablePosition, {x: 0, y: tableDimensions.y/2 + sphereDimensions.y/2, z: 0}); +var entitySphere = Entities.addEntity({ + type: "Sphere", + position: entitySpherePosition, + color: {red: 200, green: 20, blue: 200}, + dimensions: sphereDimensions +}); + +var overlaySpherePosition = Vec3.sum(tablePosition, {x: sphereDimensions.x, y: tableDimensions.y/2 + sphereDimensions.y/2, z: 0}); +var overlaySphere = Overlays.addOverlay("sphere", { + position: overlaySpherePosition, + size: 0.01, + color: { red: 20, green: 200, blue: 0}, + alpha: 1.0, + solid: true, +}); + + + +function cleanup() { + Entities.deleteEntity(table); + Entities.deleteEntity(entitySphere); + Overlays.deleteOverlay(overlaySphere); + + +} +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/toybox/bow/bow.js b/examples/toybox/bow/bow.js index 660666b8b7..1bc2ab3ecc 100644 --- a/examples/toybox/bow/bow.js +++ b/examples/toybox/bow/bow.js @@ -540,7 +540,7 @@ var arrowProperties = { dynamic: true, collisionless: false, - collisionMask: "static,dynamic,otherAvatar", // workaround: not with kinematic --> no collision with bow + collidesWith: "static,dynamic,otherAvatar", // workaround: not with kinematic --> no collision with bow velocity: releaseVelocity, gravity: ARROW_GRAVITY, lifetime: 10, diff --git a/examples/toybox/pistol/createPistol.js b/examples/toybox/pistol/createPistol.js index 8449a2234a..e5f9391c65 100644 --- a/examples/toybox/pistol/createPistol.js +++ b/examples/toybox/pistol/createPistol.js @@ -9,8 +9,8 @@ var pistol = Entities.addEntity({ position: center, dimensions: { x: 0.05, - y: .23, - z: .36 + y: 0.23, + z: 0.36 }, script: scriptURL, color: { @@ -20,9 +20,14 @@ var pistol = Entities.addEntity({ }, shapeType: 'box', dynamic: true, - gravity: {x: 0, y: -5.0, z: 0}, + gravity: { + x: 0, + y: -5.0, + z: 0 + }, restitution: 0, - collisionSoundURL: "https://s3.amazonaws.com/hifi-public/sounds/Guns/Gun_Drop_and_Metalli_1.wav", + damping:0.5, + collisionSoundURL: "http://hifi-content.s3.amazonaws.com/james/pistol/sounds/drop.wav", userData: JSON.stringify({ grabbableKey: { spatialKey: { diff --git a/examples/toybox/pistol/pistol.js b/examples/toybox/pistol/pistol.js index 6ed3407ea9..c2456d4b7d 100644 --- a/examples/toybox/pistol/pistol.js +++ b/examples/toybox/pistol/pistol.js @@ -1,6 +1,5 @@ // // pistol.js -// examples/toybox/entityScripts // // Created by Eric Levin on11/11/15. // Copyright 2015 High Fidelity, Inc. @@ -16,7 +15,6 @@ Script.include("../../libraries/constants.js"); var _this; - // if the trigger value goes below this while held, the can will stop spraying. if it goes above, it will spray var DISABLE_LASER_THRESHOLD = 0.2; var TRIGGER_CONTROLS = [ Controller.Standard.LT, diff --git a/examples/utilities/tools/renderEngineDebug.js b/examples/utilities/tools/renderEngineDebug.js index 3e619dbd5a..5d38561eda 100755 --- a/examples/utilities/tools/renderEngineDebug.js +++ b/examples/utilities/tools/renderEngineDebug.js @@ -12,7 +12,7 @@ Script.include("cookies.js"); var MENU = "Developer>Render>Debug Deferred Buffer"; -var ACTIONS = ["Off", "Diffuse", "Alpha", "Specular", "Roughness", "Normal", "Depth", "Lighting", "Custom"]; +var ACTIONS = ["Off", "Diffuse", "Metallic", "Roughness", "Normal", "Depth", "Lighting", "Shadow", "PyramidDepth", "AmbientOcclusion", "OcclusionBlurred", "Custom"]; var SETTINGS_KEY = "EngineDebugScript.DebugMode"; Number.prototype.clamp = function(min, max) { @@ -52,6 +52,7 @@ var overlaysCounter = new CounterWidget(panel, "Overlays", Render.overlay3D); var resizing = false; var previousMode = Settings.getValue(SETTINGS_KEY, -1); +previousMode = 8; Menu.addActionGroup(MENU, ACTIONS, ACTIONS[previousMode + 1]); Render.deferredDebugMode = previousMode; Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 }; // Reset to default size @@ -98,12 +99,70 @@ panel.newSlider("Tone Mapping Exposure", -10, 10, function() { return Render.tone.exposure; }, function (value) { return (value); }); +panel.newSlider("Ambient Occlusion Resolution Level", 0.0, 4.0, + function (value) { Render.ambientOcclusion.resolutionLevel = value; }, + function() { return Render.ambientOcclusion.resolutionLevel; }, + function (value) { return (value); }); + +panel.newSlider("Ambient Occlusion Radius", 0.0, 2.0, + function (value) { Render.ambientOcclusion.radius = value; }, + function() { return Render.ambientOcclusion.radius; }, + function (value) { return (value.toFixed(2)); }); + +panel.newSlider("Ambient Occlusion Level", 0.0, 1.0, + function (value) { Render.ambientOcclusion.level = value; }, + function() { return Render.ambientOcclusion.level; }, + function (value) { return (value.toFixed(2)); }); + +panel.newSlider("Ambient Occlusion Num Samples", 1, 32, + function (value) { Render.ambientOcclusion.numSamples = value; }, + function() { return Render.ambientOcclusion.numSamples; }, + function (value) { return (value); }); + +panel.newSlider("Ambient Occlusion Num Spiral Turns", 0.0, 30.0, + function (value) { Render.ambientOcclusion.numSpiralTurns = value; }, + function() { return Render.ambientOcclusion.numSpiralTurns; }, + function (value) { return (value.toFixed(2)); }); + +panel.newCheckbox("Ambient Occlusion Dithering", + function (value) { Render.ambientOcclusion.ditheringEnabled = value; }, + function() { return Render.ambientOcclusion.ditheringEnabled; }, + function (value) { return (value); }); + +panel.newSlider("Ambient Occlusion Falloff Bias", 0.0, 0.2, + function (value) { Render.ambientOcclusion.falloffBias = value; }, + function() { return Render.ambientOcclusion.falloffBias; }, + function (value) { return (value.toFixed(2)); }); + +panel.newSlider("Ambient Occlusion Edge Sharpness", 0.0, 1.0, + function (value) { Render.ambientOcclusion.edgeSharpness = value; }, + function() { return Render.ambientOcclusion.edgeSharpness; }, + function (value) { return (value.toFixed(2)); }); + +panel.newSlider("Ambient Occlusion Blur Radius", 0.0, 6.0, + function (value) { Render.ambientOcclusion.blurRadius = value; }, + function() { return Render.ambientOcclusion.blurRadius; }, + function (value) { return (value); }); + +panel.newSlider("Ambient Occlusion Blur Deviation", 0.0, 3.0, + function (value) { Render.ambientOcclusion.blurDeviation = value; }, + function() { return Render.ambientOcclusion.blurDeviation; }, + function (value) { return (value.toFixed(2)); }); + + +panel.newSlider("Ambient Occlusion GPU time", 0.0, 10.0, + function (value) {}, + function() { return Render.ambientOcclusion.gpuTime; }, + function (value) { return (value.toFixed(2) + " ms"); }); + + var tickTackPeriod = 500; function updateCounters() { opaquesCounter.update(); transparentsCounter.update(); overlaysCounter.update(); + panel.update("Ambient Occlusion GPU time"); } Script.setInterval(updateCounters, tickTackPeriod); diff --git a/examples/widgets/weatherBox.js b/examples/widgets/weatherBox.js new file mode 100644 index 0000000000..71457136e9 --- /dev/null +++ b/examples/widgets/weatherBox.js @@ -0,0 +1,161 @@ +// +// weatherBox.js +// examples/widgets/weatherBox +// +// Created by Eric Levin on 1/20/16. +// Copyright 2016 High Fidelity, Inc. +// +// This script creates a weatherBox widget which (in final version) will display the current weather +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +Script.include("../libraries/utils.js"); +var weatherBox, boxDimensions; +var orientation = Camera.getOrientation(); +orientation = Quat.safeEulerAngles(orientation); +orientation.x = 0; +orientation = Quat.fromVec3Degrees(orientation); +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); + +var emitters = []; +var spheres = []; + +createWeatherBox(center); +center.y += boxDimensions.y / 2; +createCloud(center); +for (var i = 0; i < 7; i++) { + createLightningStrike(center) +} + +function createLightningStrike(basePosition) { + var normal = Vec3.subtract(position, MyAvatar.position); + normal.y = 0; + var textureURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/textures/lightning.png" + var linePoints = []; + var normals = []; + var strokeWidths = []; + var strokeWidth = 0.02 + var currentPointPosition = {x: 0, y: 0, z: 0}; + for (var i = 0; i < 8; i++) { + linePoints.push(currentPointPosition); + currentPointPosition = Vec3.sum(currentPointPosition, {x: randFloat(-0.05, 0.05), y: randFloat(-0.1, -0.05), z: randFloat(-0.05, 0.05)}); + linePoints.push(currentPointPosition); + normals.push(normal); + normals.push(normal); + strokeWidth -= 0.002; + strokeWidths.push(strokeWidth); + strokeWidths.push(strokeWidth); + } + var position = Vec3.sum(basePosition, {x: randFloat(-boxDimensions.x/20, boxDimensions.x/20), y: 0, z: randFloat(-boxDimensions.x/20, boxDimensions.x/20)}); + var bolt = Entities.addEntity({ + type: "PolyLine", + textures: textureURL, + position: position, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + linePoints: linePoints, + normals: normals, + strokeWidths: strokeWidths, + lifetime: randFloat(0.01, 0.2) + }); + + Script.setTimeout(function() { + createLightningStrike(position) + }, randInt(500, 5000)); + +} + + +function createWeatherBox(position) { + var naturalDimensions = { + x: 1.11, + y: 1.3, + z: 1.11 + }; + boxDimensions = Vec3.multiply(naturalDimensions, 0.7); + var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/weatherBox.fbx" + weatherBox = Entities.addEntity({ + type: "Model", + name: "Weather Box", + modelURL: modelURL, + position: position, + dimensions: boxDimensions + }); +} + + +function createCloud(position) { + var props = { + "type": "ParticleEffect", + "position": position, + "isEmitting": true, + "maxParticles": 10000, + "lifespan": 3, + "emitRate": 2000, + "emitSpeed": 0.025, + "speedSpread": 0, + "emitDimensions": { + x: boxDimensions.x, + y: boxDimensions.x, + z: 0.1 + }, + "emitRadiusStart": 1, + "polarStart": 0, + "polarFinish": 1.570796012878418, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.04, + "radiusSpread": 0.02, + "radiusStart": 0.04, + radiusFinish: 0.04, + "colorStart": { + "red": 40, + "green": 40, + "blue": 100 + }, + color: { + red: 40, + green: 40, + blue: 100 + }, + "colorFinish": { + "red": 40, + "green": 40, + "blue": 100 + }, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 0.3, + "alphaFinish": 0, + "emitterShouldTrail": true, + "textures": "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png" + } + + var oceanEmitter = Entities.addEntity(props); + emitters.push(oceanEmitter); +} + +function cleanup() { + emitters.forEach(function(emitter) { + Entities.deleteEntity(emitter); + }); + spheres.forEach(function(sphere) { + Entities.deleteEntity(sphere); + }); + Entities.deleteEntity(weatherBox); + +} + +Script.scriptEnding.connect(cleanup); diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index f65ff4a8cf..2baa7a13a7 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -27,7 +27,7 @@ IceServer::IceServer(int argc, char* argv[]) : _id(QUuid::createUuid()), _serverSocket(), _activePeers(), - _httpManager(ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this) + _httpManager(QHostAddress::AnyIPv4, ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this) { // start the ice-server socket qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 5d96b95624..b4ce53b92a 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -20,12 +20,10 @@ find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistToolsMacros) if (WIN32) - add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h - add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines + add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h + add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines endif() -include_application_version() - # grab the implementation and header files from src dirs file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") @@ -45,8 +43,8 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS - Gui Multimedia Network OpenGL Qml Quick Script Svg +find_package(Qt5 COMPONENTS + Gui Multimedia Network OpenGL Qml Quick Script Svg WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets) # grab the ui files in resources/ui @@ -65,23 +63,24 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") if (APPLE) # configure CMake to use a custom Info.plist - SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in ) + set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) - set(MACOSX_BUNDLE_BUNDLE_NAME Interface) - set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.Interface) - - if (UPPER_CMAKE_BUILD_TYPE MATCHES RELEASE OR UPPER_CMAKE_BUILD_TYPE MATCHES RELWITHDEBINFO) - set(ICON_FILENAME "interface.icns") + if (PRODUCTION_BUILD) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface) else () - set(ICON_FILENAME "interface-beta.icns") + if (DEV_BUILD) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface-dev) + elseif (PR_BUILD) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface-pr) + endif () endif () - + # set how the icon shows up in the Info.plist file - SET(MACOSX_BUNDLE_ICON_FILE "${ICON_FILENAME}") + set(MACOSX_BUNDLE_ICON_FILE "${INTERFACE_ICON_FILENAME}") # set where in the bundle to put the resources file - SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set(DISCOVERED_RESOURCES "") # use the add_resources_to_os_x_bundle macro to recurse into resources @@ -89,28 +88,37 @@ if (APPLE) # append the discovered resources to our list of interface sources list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES}) - - set(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME}") + + set(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}") endif() # create the executable, make it a bundle on OS X if (APPLE) add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) + + # make sure the output name for the .app bundle is correct + set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${INTERFACE_BUNDLE_NAME}) elseif(WIN32) - add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM}) + # configure an rc file for the chosen icon + set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}") + set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc") + configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT}) + + # add an executable that also has the icon itself and the configured rc file as resources + add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT}) else() add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif() -# These are external plugins, but we need to do the 'add dependency' here so that their -# binary directories get added to the fixup path -add_dependency_external_projects(sixense) -add_dependency_external_projects(sdl2) +target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") + if (WIN32) - add_dependency_external_projects(OpenVR) -endif() -if(WIN32 OR APPLE) - add_dependency_external_projects(neuron) + # These are external plugins, but we need to do the 'add dependency' here so that their + # binary directories get added to the fixup path + add_dependency_external_projects(sixense) + add_dependency_external_projects(sdl2) + add_dependency_external_projects(OpenVR) + add_dependency_external_projects(neuron) endif() # disable /OPT:REF and /OPT:ICF for the Debug builds @@ -121,11 +129,14 @@ if (WIN32) endif() # link required hifi libraries -link_hifi_libraries(shared octree environment gpu gl procedural model render - recording fbx networking model-networking entities avatars - audio audio-client animation script-engine physics +link_hifi_libraries(shared octree gpu gl procedural model render + recording fbx networking model-networking entities avatars + audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater - controllers plugins display-plugins input-plugins ) + controllers plugins display-plugins input-plugins) + +# include the binary directory of render-utils for shader includes +target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") #fixme find a way to express faceshift as a plugin target_bullet() @@ -133,39 +144,39 @@ target_glew() target_opengl() if (WIN32 OR APPLE) - target_faceshift() + target_faceshift() endif() # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) - + if (${${EXTERNAL}_UPPERCASE}_REQUIRED) find_package(${EXTERNAL} REQUIRED) else () find_package(${EXTERNAL}) endif () - + if (${${EXTERNAL}_UPPERCASE}_FOUND AND NOT DISABLE_${${EXTERNAL}_UPPERCASE}) add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE}) - + # include the library directories (ignoring warnings) if (NOT ${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS) set(${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIR}) endif () - + include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) - + # perform the system include hack for OS X to ignore warnings if (APPLE) foreach(EXTERNAL_INCLUDE_DIR ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${EXTERNAL_INCLUDE_DIR}") endforeach() endif () - + if (NOT ${${EXTERNAL}_UPPERCASE}_LIBRARIES) set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY}) endif () - + if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE") target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES}) elseif (APPLE AND NOT INSTALLER_BUILD) @@ -179,15 +190,15 @@ include_directories("${PROJECT_SOURCE_DIR}/src") target_link_libraries( ${TARGET_NAME} - Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL - Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL + Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets ) -# Issue causes build failure unless we add this directory. -# See https://bugreports.qt.io/browse/QTBUG-43351 +# Issue causes build failure unless we add this directory. +# See https://bugreports.qt.io/browse/QTBUG-43351 if (WIN32) - add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine) + add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine) endif() # assume we are using a Qt build without bearer management @@ -199,31 +210,63 @@ if (APPLE) find_library(AppKit AppKit) target_link_libraries(${TARGET_NAME} ${OpenGL} ${AppKit}) - - # install command for OS X bundle - INSTALL(TARGETS ${TARGET_NAME} - BUNDLE DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/install" COMPONENT Runtime - RUNTIME DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/install" COMPONENT Runtime + + # setup install of OS X interface bundle + install(TARGETS ${TARGET_NAME} + BUNDLE DESTINATION ${INTERFACE_INSTALL_DIR} + COMPONENT ${CLIENT_COMPONENT} ) + + set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") + + # call the fixup_interface macro to add required bundling commands for installation + fixup_interface() + else (APPLE) # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources" $/resources + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/examples" + $/scripts ) # link target to external libraries if (WIN32) target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib) - else (WIN32) - # Nothing else required on linux apparently + + # setup install of executable and things copied by fixup/windeployqt + install( + FILES "$/" + DESTINATION ${INTERFACE_INSTALL_DIR} + COMPONENT ${CLIENT_COMPONENT} + ) + + set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_DIR}") + + set(EXECUTABLE_COMPONENT ${CLIENT_COMPONENT}) + + optional_win_executable_signing() endif() endif (APPLE) -if (WIN32) - set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml") +if (SCRIPTS_INSTALL_DIR) + # setup install of scripts beside interface executable + install( + DIRECTORY "${CMAKE_SOURCE_DIR}/examples/" + DESTINATION ${SCRIPTS_INSTALL_DIR}/scripts + COMPONENT ${CLIENT_COMPONENT} + ) endif() -package_libraries_for_deployment() -consolidate_stack_components() +if (WIN32) + set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml") + + set(TARGET_INSTALL_DIR ${INTERFACE_INSTALL_DIR}) + set(TARGET_INSTALL_COMPONENT ${CLIENT_COMPONENT}) + manually_install_ssl_eay() + + package_libraries_for_deployment() +endif() diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index a622931db7..fd4e629568 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -16,8 +16,6 @@ Windows.Window { destroyOnCloseButton: false property alias source: webview.url - function raiseWindow() { Desktop.raise(root) } - Controls.WebView { id: webview url: "about:blank" diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 2a8d8f60d9..63efd0bc2e 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -21,16 +21,10 @@ Windows.Window { destroyOnCloseButton: false property alias source: pageLoader.source - function raiseWindow() { Desktop.raise(root) } - Loader { id: pageLoader objectName: "Loader" focus: true property var dialog: root - - Keys.onPressed: { - console.log("QmlWindow pageLoader keypress") - } } } // dialog diff --git a/interface/resources/qml/VrMenu.qml b/interface/resources/qml/VrMenu.qml deleted file mode 100644 index 689171f9e9..0000000000 --- a/interface/resources/qml/VrMenu.qml +++ /dev/null @@ -1,229 +0,0 @@ -import Hifi 1.0 as Hifi - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 - -import "controls" -import "styles" - - -Hifi.VrMenu { - id: root - HifiConstants { id: hifi } - - anchors.fill: parent - - objectName: "VrMenu" - enabled: false - opacity: 0.0 - z: 10000 - - property int animationDuration: 200 - property var models: [] - property var columns: [] - - onEnabledChanged: { - if (enabled && columns.length == 0) { - pushColumn(rootMenu.items); - } - opacity = enabled ? 1.0 : 0.0 - offscreenFlags.navigationFocused = enabled; - } - - // The actual animator - Behavior on opacity { - NumberAnimation { - duration: root.animationDuration - easing.type: Easing.InOutBounce - } - } - - onOpacityChanged: { - visible = (opacity != 0.0); - } - - onVisibleChanged: { - if (!visible) reset(); - } - - property var menuBuilder: Component { - VrMenuView { - property int menuDepth: root.models.length - 1 - model: root.models[menuDepth] - - function fit(position, size, maxposition) { - var padding = 8; - if (position < padding) { - position = padding; - } else if (position + size + padding > maxposition) { - position = maxposition - (size + padding); - } - return position; - } - - Component.onCompleted: { - if (menuDepth === 0) { - x = lastMousePosition.x - 20 - y = lastMousePosition.y - 20 - } else { - var lastColumn = root.columns[menuDepth - 1] - x = lastColumn.x + 64; - y = lastMousePosition.y - height / 2; - } - x = fit(x, width, parent.width); - y = fit(y, height, parent.height); - } - - onSelected: { - root.selectItem(menuDepth, item) - } - } - } - - function lastColumn() { - return columns[root.columns.length - 1]; - } - - function pushColumn(items) { - models.push(itemsToModel(items)) - if (columns.length) { - var oldColumn = lastColumn(); - //oldColumn.enabled = false - } - var newColumn = menuBuilder.createObject(root); - columns.push(newColumn); - forceActiveFocus(); - } - - function popColumn() { - if (columns.length > 0) { - var curColumn = columns.pop(); - curColumn.visible = false; - curColumn.destroy(); - models.pop(); - } - - if (columns.length == 0) { - enabled = false; - return; - } - - curColumn = lastColumn(); - curColumn.enabled = true; - curColumn.opacity = 1.0; - curColumn.forceActiveFocus(); - } - - function itemsToModel(items) { - var newListModel = Qt.createQmlObject('import QtQuick 2.2; ListModel {}', root); - for (var i = 0; i < items.length; ++i) { - var item = items[i]; - switch (item.type) { - case 2: - newListModel.append({"type":item.type, "name": item.title, "item": item}) - break; - case 1: - newListModel.append({"type":item.type, "name": item.text, "item": item}) - break; - case 0: - newListModel.append({"type":item.type, "name": "-----", "item": item}) - break; - } - } - return newListModel; - } - - function selectItem(depth, source) { - var popped = false; - while (depth + 1 < columns.length) { - popColumn() - popped = true - } - - switch (source.type) { - case 2: - lastColumn().enabled = false - pushColumn(source.items) - break; - case 1: - if (!popped) source.trigger() - enabled = false - break; - case 0: - break; - } - } - - function reset() { - while (columns.length > 0) { - popColumn(); - } - } - - MouseArea { - anchors.fill: parent - id: mouseArea - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: { - if (mouse.button == Qt.RightButton) { - root.popColumn(); - } else { - root.enabled = false; - } - } - } - - function addMenu(menu, newMenu) { - return menu.addMenu(newMenu); - } - - function addItem(menu, newMenuItem) { - return menu.addItem(newMenuItem); - } - - function insertItem(menu, beforeItem, newMenuItem) { - for (var i = 0; i < menu.items.length; ++i) { - if (menu.items[i] === beforeItem) { - return menu.insertItem(i, newMenuItem); - } - } - return addItem(menu, newMenuItem); - } - - function removeItem(menu, menuItem) { - menu.removeItem(menuItem); - } - - function previousItem() { - if (columns.length) { - lastColumn().incrementCurrentIndex() - } - } - - function nextItem() { - if (columns.length) { - lastColumn().decrementCurrentIndex() - } - } - - function selectCurrentItem() { - if (columns.length) { - var depth = columns.length - 1; - var index = lastColumn().currentIndex; - if (index >= 0) { - var model = models[depth]; - var item = model.get(index).item; - selectItem(depth, item); - } - } - } - - Keys.onDownPressed: previousItem(); - Keys.onUpPressed: nextItem(); - Keys.onSpacePressed: selectCurrentItem(); - Keys.onReturnPressed: selectCurrentItem(); - Keys.onRightPressed: selectCurrentItem(); - Keys.onLeftPressed: popColumn(); - Keys.onEscapePressed: popColumn(); -} diff --git a/interface/resources/qml/VrMenuView.qml b/interface/resources/qml/VrMenuView.qml deleted file mode 100644 index b00e21ba93..0000000000 --- a/interface/resources/qml/VrMenuView.qml +++ /dev/null @@ -1,77 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 - -import "styles" - -ListView { - id: root - HifiConstants { id: hifi } - width: 128 - height: count * 32 - onEnabledChanged: recalcSize(); - onVisibleChanged: recalcSize(); - onCountChanged: recalcSize(); - - signal selected(var item) - - highlight: Rectangle { - width: root.currentItem ? root.currentItem.width : 0 - height: root.currentItem ? root.currentItem.height : 0 - color: "lightsteelblue"; radius: 3 - } - - delegate: VrMenuItem { - text: name - source: item - onImplicitHeightChanged: root.recalcSize() - onImplicitWidthChanged: root.recalcSize() - - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: root.currentIndex = index - onClicked: root.selected(item) - } - } - - function recalcSize() { - if (model.count !== count || !visible) { - return; - } - - var originalIndex = currentIndex; - var maxWidth = width; - var newHeight = 0; - for (var i = 0; i < count; ++i) { - currentIndex = i; - if (!currentItem) { - continue; - } - if (currentItem && currentItem.implicitWidth > maxWidth) { - maxWidth = currentItem.implicitWidth - } - if (currentItem.visible) { - newHeight += currentItem.implicitHeight - } - } - if (maxWidth > width) { - width = maxWidth; - } - if (newHeight > height) { - height = newHeight - } - currentIndex = originalIndex; - } - - Border { - id: border - anchors.fill: parent - anchors.margins: -8 - z: parent.z - 1 - border.color: hifi.colors.hifiBlue - color: hifi.colors.window - } -} - - diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/desktop/Desktop.qml similarity index 81% rename from interface/resources/qml/Root.qml rename to interface/resources/qml/desktop/Desktop.qml index e1431de967..1996834378 100644 --- a/interface/resources/qml/Root.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -2,35 +2,15 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs; -import "dialogs" +import "../dialogs" +import "../menus" // This is our primary 'desktop' object to which all VR dialogs and // windows will be childed. FocusScope { id: desktop anchors.fill: parent; - - // Debugging help for figuring out focus issues - property var offscreenWindow; - onOffscreenWindowChanged: offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged); - function onWindowFocusChanged() { - console.log("Focus item is " + offscreenWindow.activeFocusItem); - var focusedItem = offscreenWindow.activeFocusItem ; - if (DebugQML && focusedItem) { - var rect = desktop.mapToItem(desktop, focusedItem.x, focusedItem.y, focusedItem.width, focusedItem.height); - focusDebugger.visible = true - focusDebugger.x = rect.x; - focusDebugger.y = rect.y; - focusDebugger.width = rect.width - focusDebugger.height = rect.height - } - } - - Rectangle { - id: focusDebugger; - z: 9999; visible: false; color: "red" - ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 } - } + objectName: "desktop" // Allows QML/JS to find the desktop through the parent chain property bool desktopRoot: true @@ -38,38 +18,13 @@ FocusScope { // The VR version of the primary menu property var rootMenu: Menu { objectName: "rootMenu" } - // The tool window, one instance - property alias toolWindow: toolWindow - ToolWindow { id: toolWindow } - - // FIXME support always on top flags - function raise(item) { - d.raiseWindow(item); - } - - Component { - id: messageDialogBuilder - MessageDialog { } - } - - Component { - id: nativeMessageDialogBuilder - OriginalDialogs.MessageDialog { } - } - - function messageBox(properties) { - // Debugging: native message dialog for comparison - // nativeMessageDialogBuilder.createObject(desktop, properties); - return messageDialogBuilder.createObject(desktop, properties); - } - QtObject { id: d - readonly property int zBasisNormal: 0 readonly property int zBasisAlwaysOnTop: 4096 readonly property int zBasisModal: 8192 - + readonly property var messageDialogBuilder: Component { MessageDialog { } } + readonly property var nativeMessageDialogBuilder: Component { OriginalDialogs.MessageDialog { } } function findChild(item, name) { for (var i = 0; i < item.children.length; ++i) { @@ -203,6 +158,43 @@ FocusScope { } } + MenuMouseHandler { id: menuPopperUpper } + + function raise(item) { + d.raiseWindow(item); + } + + function messageBox(properties) { + // Debugging: native message dialog for comparison + // d.nativeMessageDialogBuilder.createObject(desktop, properties); + return d.messageDialogBuilder.createObject(desktop, properties); + } + + function popupMenu(point) { + menuPopperUpper.popup(desktop, rootMenu.items, point); + } + + function toggleMenu(point) { + menuPopperUpper.toggle(desktop, rootMenu.items, point); + } + + Keys.onEscapePressed: { + if (menuPopperUpper.closeLastMenu()) { + event.accepted = true; + return; + } + event.accepted = false; + } + + Keys.onLeftPressed: { + if (menuPopperUpper.closeLastMenu()) { + event.accepted = true; + return; + } + event.accepted = false; + } + + function unfocusWindows() { var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { @@ -210,6 +202,36 @@ FocusScope { } desktop.focus = true; } + + // Debugging help for figuring out focus issues + property var offscreenWindow; + onOffscreenWindowChanged: { + offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged); + focusHack.start(); + } + + FocusHack { id: focusHack; } + + function onWindowFocusChanged() { + console.log("Focus item is " + offscreenWindow.activeFocusItem); + var focusedItem = offscreenWindow.activeFocusItem ; + if (DebugQML && focusedItem) { + var rect = desktop.mapToItem(null, focusedItem.x, focusedItem.y, focusedItem.width, focusedItem.height); + focusDebugger.visible = true + focusDebugger.x = rect.x; + focusDebugger.y = rect.y; + focusDebugger.width = rect.width + focusDebugger.height = rect.height + } + } + + Rectangle { + id: focusDebugger; + z: 9999; visible: false; color: "red" + ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 } + } + + } diff --git a/interface/resources/qml/desktop/FocusHack.qml b/interface/resources/qml/desktop/FocusHack.qml new file mode 100644 index 0000000000..7514705983 --- /dev/null +++ b/interface/resources/qml/desktop/FocusHack.qml @@ -0,0 +1,26 @@ +import QtQuick 2.5 + +FocusScope { + id: root + + TextInput { + id: textInput; + focus: true + width: 10; height: 10 + onActiveFocusChanged: root.destroy() + } + + Timer { + id: focusTimer + running: false + interval: 100 + onTriggered: textInput.forceActiveFocus() + } + + function start() { + focusTimer.running = true; + } +} + + + diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index fcaf994b1b..4c02c94f03 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -8,23 +8,20 @@ import "../windows" import "../styles" // Work in progress.... -Window { +ModalWindow { id: root HifiConstants { id: hifi } signal selectedFile(var file); signal canceled(); - - anchors.centerIn: parent resizable: true width: 640 height: 480 - modality: Qt.ApplicationModal + property string settingsName: "" property alias folder: folderModel.folder property alias filterModel: selectionType.model - Rectangle { anchors.fill: parent color: "white" diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 6f96340be6..5b9a653f2e 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -7,19 +7,14 @@ import "../styles" import "../windows" // FIXME respect default button functionality -// FIXME force active focus at all times (modal dialog) -Window { +ModalWindow { id: root HifiConstants { id: hifi } - implicitWidth: 640 implicitHeight: 320 destroyOnCloseButton: true destroyOnInvisible: true visible: true - modality: Qt.ApplicationModal - anchors.centerIn: parent - frame: ModalFrame {} signal selected(int button); diff --git a/interface/resources/qml/dialogs/RunningScripts.qml b/interface/resources/qml/dialogs/RunningScripts.qml index 0d771b79cd..7668d4e197 100644 --- a/interface/resources/qml/dialogs/RunningScripts.qml +++ b/interface/resources/qml/dialogs/RunningScripts.qml @@ -14,6 +14,7 @@ Window { resizable: true destroyOnInvisible: true x: 40; y: 40 + implicitWidth: 384; implicitHeight: 640 property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter @@ -77,7 +78,7 @@ Window { } function loadFromFile() { - var fileDialog = fileDialogBuilder.createObject(Desktop, { filterModel: fileFilters }); + var fileDialog = fileDialogBuilder.createObject(desktop, { filterModel: fileFilters }); fileDialog.canceled.connect(function(){ console.debug("Cancelled file open") }) @@ -90,7 +91,7 @@ Window { Rectangle { color: "white" - implicitWidth: 384; implicitHeight: 640 + anchors.fill: parent Item { anchors { fill: parent; margins: 8 } diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml new file mode 100644 index 0000000000..1655a32989 --- /dev/null +++ b/interface/resources/qml/hifi/Desktop.qml @@ -0,0 +1,15 @@ +import QtQuick 2.5 + +import "../desktop" +import ".." + +Desktop { + id: desktop + + // The tool window, one instance + property alias toolWindow: toolWindow + ToolWindow { id: toolWindow } +} + + + diff --git a/interface/resources/qml/menus/MenuMouseHandler.qml b/interface/resources/qml/menus/MenuMouseHandler.qml new file mode 100644 index 0000000000..c73730773f --- /dev/null +++ b/interface/resources/qml/menus/MenuMouseHandler.qml @@ -0,0 +1,149 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "." + +Item { + id: root + property int zBasis: 8192 - 1024 + anchors.fill: parent + + MouseArea { + id: menuRoot; + anchors.fill: parent + enabled: d.topMenu !== null + onClicked: { + d.clearMenus(); + } + } + + QtObject { + id: d + property var menuStack: [] + property var topMenu: null; + property var modelMaker: Component { ListModel { } } + property var menuViewMaker: Component { + VrMenuView { + id: subMenu + onSelected: d.handleSelection(subMenu, currentItem, item) + } + } + + function toModel(items) { + var result = modelMaker.createObject(desktop); + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + if (!item.visible) continue; + switch (item.type) { + case MenuItemType.Menu: + result.append({"name": item.title, "item": item}) + break; + case MenuItemType.Item: + result.append({"name": item.text, "item": item}) + break; + case MenuItemType.Separator: + result.append({"name": "", "item": item}) + break; + } + } + return result; + } + + function popMenu() { + if (menuStack.length) { + menuStack.pop().destroy(); + } + if (menuStack.length) { + topMenu = menuStack[menuStack.length - 1]; + topMenu.focus = true; + } else { + topMenu = null; + offscreenFlags.navigationFocused = false; + menuRoot.enabled = false; + } + } + + function pushMenu(newMenu) { + menuStack.push(newMenu); + topMenu = newMenu; + topMenu.focus = true; + offscreenFlags.navigationFocused = true; + } + + function clearMenus() { + while (menuStack.length) { + popMenu() + } + } + + function clampMenuPosition(menu) { + var margins = 0; + if (menu.x < margins) { + menu.x = margins + } else if ((menu.x + menu.width + margins) > root.width) { + menu.x = root.width - (menu.width + margins); + } + + if (menu.y < 0) { + menu.y = margins + } else if ((menu.y + menu.height + margins) > root.height) { + menu.y = root.height - (menu.height + margins); + } + } + + function buildMenu(items, targetPosition) { + var model = toModel(items); + var newMenu = menuViewMaker.createObject(menuRoot, { model: model, z: topMenu ? topMenu.z + 1 : zBasis }); + if (targetPosition) { + newMenu.x = targetPosition.x + newMenu.y = targetPosition.y - newMenu.height / 3 * 1 + } + clampMenuPosition(newMenu); + pushMenu(newMenu); + return newMenu; + } + + function handleSelection(parentMenu, selectedItem, item) { + while (topMenu && topMenu !== parentMenu) { + popMenu(); + } + + switch (item.type) { + case MenuItemType.Menu: + var target = Qt.vector2d(topMenu.x, topMenu.y).plus(Qt.vector2d(selectedItem.x + 96, selectedItem.y)); + buildMenu(item.items, target).objectName = item.title; + break; + + case MenuItemType.Item: + console.log("Triggering " + item.text) + item.trigger(); + clearMenus(); + break; + } + } + + } + + function popup(parent, items, point) { + d.clearMenus(); + menuRoot.enabled = true; + d.buildMenu(items, point); + } + + function toggle(parent, items, point) { + if (d.topMenu) { + d.clearMenus(); + return; + } + popup(parent, items, point); + } + + function closeLastMenu() { + if (d.menuStack.length) { + d.popMenu(); + return true; + } + return false; + } + +} diff --git a/interface/resources/qml/VrMenuItem.qml b/interface/resources/qml/menus/VrMenuItem.qml similarity index 94% rename from interface/resources/qml/VrMenuItem.qml rename to interface/resources/qml/menus/VrMenuItem.qml index 2b1a4a3b5a..c23a54a2c7 100644 --- a/interface/resources/qml/VrMenuItem.qml +++ b/interface/resources/qml/menus/VrMenuItem.qml @@ -1,16 +1,13 @@ import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 -import "controls" -import "styles" + +import "../controls" +import "../styles" Item { id: root - HifiConstants { - id: hifi - } - - // The model object + HifiConstants { id: hifi } property alias text: label.text property var source diff --git a/interface/resources/qml/menus/VrMenuView.qml b/interface/resources/qml/menus/VrMenuView.qml new file mode 100644 index 0000000000..bbb9bd706e --- /dev/null +++ b/interface/resources/qml/menus/VrMenuView.qml @@ -0,0 +1,100 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 + +import "../styles" + + +FocusScope { + id: root + implicitHeight: border.height + implicitWidth: border.width + + property alias currentItem: listView.currentItem + property alias model: listView.model + signal selected(var item) + + + Border { + id: border + anchors.fill: listView + anchors.margins: -8 + border.color: hifi.colors.hifiBlue + color: hifi.colors.window + // color: "#7f7f7f7f" + } + + ListView { + id: listView + x: 8; y: 8 + HifiConstants { id: hifi } + width: 128 + height: count * 32 + onEnabledChanged: recalcSize(); + onVisibleChanged: recalcSize(); + onCountChanged: recalcSize(); + focus: true + + highlight: Rectangle { + width: listView.currentItem ? listView.currentItem.width : 0 + height: listView.currentItem ? listView.currentItem.height : 0 + color: "lightsteelblue"; radius: 3 + } + + delegate: VrMenuItem { + text: name + source: item + onImplicitHeightChanged: listView.recalcSize() + onImplicitWidthChanged: listView.recalcSize() + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: listView.currentIndex = index + onClicked: root.selected(item) + } + } + + function recalcSize() { + if (model.count !== count || !visible) { + return; + } + + var originalIndex = currentIndex; + var maxWidth = width; + var newHeight = 0; + for (var i = 0; i < count; ++i) { + currentIndex = i; + if (!currentItem) { + continue; + } + if (currentItem && currentItem.implicitWidth > maxWidth) { + maxWidth = currentItem.implicitWidth + } + if (currentItem.visible) { + newHeight += currentItem.implicitHeight + } + } + if (maxWidth > width) { + width = maxWidth; + } + if (newHeight > height) { + height = newHeight + } + currentIndex = originalIndex; + } + + function previousItem() { currentIndex = (currentIndex + count - 1) % count; } + function nextItem() { currentIndex = (currentIndex + count + 1) % count; } + function selectCurrentItem() { if (currentIndex != -1) root.selected(currentItem.source); } + + Keys.onUpPressed: previousItem(); + Keys.onDownPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + } +} + + + diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index e0e9049828..2601c3899f 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -17,9 +17,10 @@ Frame { color: "#7f7f7f7f"; radius: 3; MouseArea { + enabled: window.visible anchors.fill: parent acceptedButtons: Qt.AllButtons - onClicked: { } + onClicked: {} onDoubleClicked: {} onPressAndHold: {} onReleased: {} diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml new file mode 100644 index 0000000000..eb45273ff4 --- /dev/null +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -0,0 +1,12 @@ +import QtQuick 2.5 + +import "." + +Window { + id: root + anchors.centerIn: parent + modality: Qt.ApplicationModal + frame: ModalFrame{} +} + + diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 6bc8a2bab5..21bd8f3dec 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -69,28 +69,9 @@ Fadable { // Default to a standard frame. Can be overriden to provide custom // frame styles, like a full desktop frame to simulate a modal window - property var frame; + property var frame: DefaultFrame { } - Component { - id: defaultFrameBuilder; - DefaultFrame { anchors.fill: parent } - } - - Component { - id: modalFrameBuilder; - ModalFrame { anchors.fill: parent } - } - - Component.onCompleted: { - if (!frame) { - if (modality === Qt.NonModal) { - frame = defaultFrameBuilder.createObject(window); - } else { - frame = modalFrameBuilder.createObject(window); - } - } - raise(); - } + Component.onCompleted: raise(); children: [ frame, content, activator ] @@ -114,7 +95,7 @@ Fadable { function raise() { if (visible && parent) { - Desktop.raise(window) + desktop.raise(window) if (!focus) { focus = true; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 32363fb13d..220bdca924 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -36,6 +36,9 @@ #include #include +#include +#include + #include #include #include @@ -57,7 +60,7 @@ #include #include #include -#include +#include #include #include #include @@ -303,7 +306,7 @@ bool setupEssentials(int& argc, char** argv) { listenPort = atoi(portStr); } // Set build version - QCoreApplication::setApplicationVersion(BUILD_VERSION); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); Setting::preInit(); @@ -420,9 +423,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : auto controllerScriptingInterface = DependencyManager::get().data(); _controllerScriptingInterface = dynamic_cast(controllerScriptingInterface); - // to work around the Qt constant wireless scanning, set the env for polling interval very high - const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); - qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); _entityClipboard->createRootElement(); @@ -796,7 +796,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { - VrMenu::toggle(); // show context menu even on non-stereo displays + offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QCursor::pos())); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = QCursor::pos(); auto newPos = oldPos; @@ -1179,7 +1179,6 @@ void Application::initializeUi() { AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); - VrMenu::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); @@ -1189,7 +1188,7 @@ void Application::initializeUi() { offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // support the window management and scripting proxies for VR use - offscreenUi->createDesktop(); + offscreenUi->createDesktop(QString("hifi/Desktop.qml")); // FIXME either expose so that dialogs can set this themselves or // do better detection in the offscreen UI of what has focus @@ -1247,8 +1246,6 @@ void Application::initializeUi() { rootContext->setContextProperty("Render", DependencyManager::get().data()); _glWidget->installEventFilter(offscreenUi.data()); - VrMenu::load(); - VrMenu::executeQueuedLambdas(); offscreenUi->setMouseTranslator([=](const QPointF& pt) { QPointF result = pt; auto displayPlugin = getActiveDisplayPlugin(); @@ -1886,12 +1883,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; } - case Qt::Key_A: - if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::Atmosphere); - } - break; - case Qt::Key_Backslash: Menu::getInstance()->triggerOption(MenuOption::Chat); break; @@ -2066,7 +2057,8 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) { - VrMenu::toggle(); // show context menu even on non-stereo displays + auto offscreenUi = DependencyManager::get(); + offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QCursor::pos())); } _keysPressed.remove(event->key()); @@ -2698,8 +2690,6 @@ void Application::init() { // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); - _environment.init(); - DependencyManager::get()->init(); DependencyManager::get()->init(); @@ -3620,10 +3610,6 @@ public: typedef Payload::DataPointer Pointer; Stars _stars; - Environment* _environment; - - BackgroundRenderData(Environment* environment) : _environment(environment) { - } static render::ItemID _item; // unique WorldBoxRenderData }; @@ -3665,63 +3651,8 @@ namespace render { "Application::payloadRender() ... stars..."); // should be the first rendering pass - w/o depth buffer / lighting - // compute starfield alpha based on distance from atmosphere - float alpha = 1.0f; - bool hasStars = true; - - if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { - // TODO: handle this correctly for zones - const EnvironmentData& closestData = background->_environment->getClosestData(args->_viewFrustum->getPosition()); // was theCamera instead of _viewFrustum - - if (closestData.getHasStars()) { - const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f; - const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f; - - glm::vec3 sunDirection = (args->_viewFrustum->getPosition()/*getAvatarPosition()*/ - closestData.getSunLocation()) - / closestData.getAtmosphereOuterRadius(); - float height = glm::distance(args->_viewFrustum->getPosition()/*theCamera.getPosition()*/, closestData.getAtmosphereCenter()); - if (height < closestData.getAtmosphereInnerRadius()) { - // If we're inside the atmosphere, then determine if our keyLight is below the horizon - alpha = 0.0f; - - if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { - float directionY = glm::clamp(sunDirection.y, - -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) - + APPROXIMATE_DISTANCE_FROM_HORIZON; - alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); - } - - - } else if (height < closestData.getAtmosphereOuterRadius()) { - alpha = (height - closestData.getAtmosphereInnerRadius()) / - (closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius()); - - if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { - float directionY = glm::clamp(sunDirection.y, - -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) - + APPROXIMATE_DISTANCE_FROM_HORIZON; - alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); - } - } - } else { - hasStars = false; - } - } - - // finally render the starfield - if (hasStars) { - background->_stars.render(args, alpha); - } - - // draw the sky dome - if (/*!selfAvatarOnly &&*/ Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { - PerformanceTimer perfTimer("atmosphere"); - PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "Application::displaySide() ... atmosphere..."); - - background->_environment->renderAtmospheres(batch, *(args->_viewFrustum)); - } - + static const float alpha = 1.0f; + background->_stars.render(args, alpha); } } break; @@ -3761,12 +3692,10 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se // Background rendering decision if (BackgroundRenderData::_item == 0) { - auto backgroundRenderData = make_shared(&_environment); + auto backgroundRenderData = make_shared(); auto backgroundRenderPayload = make_shared(backgroundRenderData); BackgroundRenderData::_item = _main3DScene->allocateID(); pendingChanges.resetItem(BackgroundRenderData::_item, backgroundRenderPayload); - } else { - } // Assuming nothing get's rendered through that @@ -3806,7 +3735,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se DependencyManager::get()->setAmbientLightMode(getRenderAmbientLight()); auto skyStage = DependencyManager::get()->getSkyStage(); DependencyManager::get()->setGlobalLight(skyStage->getSunLight()->getDirection(), skyStage->getSunLight()->getColor(), skyStage->getSunLight()->getIntensity(), skyStage->getSunLight()->getAmbientIntensity()); - DependencyManager::get()->setGlobalAtmosphere(skyStage->getAtmosphere()); auto skybox = model::SkyboxPointer(); if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) { @@ -3847,6 +3775,8 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se auto engineContext = _renderEngine->getRenderContext(); renderInterface->setItemCounts(engineContext->getItemsConfig()); + renderInterface->setJobGPUTimes(engineContext->getAmbientOcclusion().gpuTime); + } activeRenderingThread = nullptr; @@ -5181,3 +5111,30 @@ void Application::setActiveDisplayPlugin(const QString& pluginName) { } updateDisplayMode(); } + +void Application::handleLocalServerConnection() { + auto server = qobject_cast(sender()); + + qDebug() << "Got connection on local server from additional instance - waiting for parameters"; + + auto socket = server->nextPendingConnection(); + + connect(socket, &QLocalSocket::readyRead, this, &Application::readArgumentsFromLocalSocket); + + qApp->getWindow()->raise(); + qApp->getWindow()->activateWindow(); +} + +void Application::readArgumentsFromLocalSocket() { + auto socket = qobject_cast(sender()); + + auto message = socket->readAll(); + socket->deleteLater(); + + qDebug() << "Read from connection: " << message; + + // If we received a message, try to open it as a URL + if (message.length() > 0) { + qApp->openUrl(QString::fromUtf8(message)); + } +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 30cda33051..71946497d5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -44,7 +44,6 @@ #include "avatar/MyAvatar.h" #include "Bookmarks.h" #include "Camera.h" -#include "Environment.h" #include "FileLogger.h" #include "gpu/Context.h" #include "Menu.h" @@ -89,7 +88,7 @@ class Application; class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, public AbstractUriHandler { Q_OBJECT - + // TODO? Get rid of those friend class OctreePacketProcessor; friend class PluginContainerProxy; @@ -102,15 +101,15 @@ public: Application(int& argc, char** argv, QElapsedTimer& startup_time); ~Application(); - void postLambdaEvent(std::function f); + void postLambdaEvent(std::function f) override; void initializeGL(); void initializeUi(); void paintGL(); void resizeGL(); - bool event(QEvent* event); - bool eventFilter(QObject* object, QEvent* event); + bool event(QEvent* event) override; + bool eventFilter(QObject* object, QEvent* event) override; glm::uvec2 getCanvasSize() const; glm::uvec2 getUiSize() const; @@ -129,7 +128,7 @@ public: // passes, mirror window passes, etc ViewFrustum* getDisplayViewFrustum(); const ViewFrustum* getDisplayViewFrustum() const; - ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } + ViewFrustum* getShadowViewFrustum() override { return &_shadowViewFrustum; } const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } EntityTreeRenderer* getEntities() { return DependencyManager::get().data(); } QUndoStack* getUndoStack() { return &_undoStack; } @@ -151,7 +150,7 @@ public: Overlays& getOverlays() { return _overlays; } bool isForeground() const { return _isForeground; } - + uint32_t getFrameCount() { return _frameCount; } float getFps() const { return _fps; } float getTargetFrameRate(); // frames/second @@ -164,7 +163,7 @@ public: NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } - virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine); + virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) override; QImage renderAvatarBillboard(RenderArgs* renderArgs); @@ -172,8 +171,6 @@ public: virtual QThread* getMainThread() { return thread(); } virtual PickRay computePickRay(float x, float y) const; virtual glm::vec3 getAvatarPosition() const; - virtual void overrideEnvironmentData(const EnvironmentData& newData) { _environment.override(newData); } - virtual void endOverrideEnvironmentData() { _environment.endOverride(); } virtual qreal getDevicePixelRatio(); void setActiveDisplayPlugin(const QString& pluginName); @@ -208,9 +205,9 @@ public: void setMaxOctreePacketsPerSecond(int maxOctreePPS); int getMaxOctreePacketsPerSecond(); - render::ScenePointer getMain3DScene() { return _main3DScene; } + render::ScenePointer getMain3DScene() override { return _main3DScene; } render::ScenePointer getMain3DScene() const { return _main3DScene; } - render::EnginePointer getRenderEngine() { return _renderEngine; } + render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } const QRect& getMirrorViewRect() const { return _mirrorViewRect; } @@ -245,6 +242,9 @@ public slots: void toggleLogDialog(); void toggleRunningScriptsWidget(); + void handleLocalServerConnection(); + void readArgumentsFromLocalSocket(); + void showFriendsWindow(); void packageModel(); @@ -258,7 +258,7 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker(); - + #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); void calibrateEyeTracker1Point(); @@ -275,11 +275,11 @@ public slots: void reloadResourceCaches(); void crashApplication(); - + void rotationModeChanged(); - + void runTests(); - + private slots: void clearDomainOctreeDetails(); void idle(uint64_t now); @@ -291,12 +291,12 @@ private slots: void faceTrackerMuteToggled(); void activeChanged(Qt::ApplicationState state); - + void domainSettingsReceived(const QJsonObject& domainSettingsObject); void handleDomainConnectionDeniedPacket(QSharedPointer message); - + void notifyPacketVersionMismatch(); - + void loadSettings(); void saveSettings(); @@ -318,13 +318,13 @@ private slots: void packetSent(quint64 length); void updateDisplayMode(); void updateInputModes(); - + private: void initDisplay(); void init(); void cleanupBeforeQuit(); - + void emptyLocalCache(); void update(float deltaTime); @@ -344,45 +344,45 @@ private: void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool billboard = false); int sendNackPackets(); - + void takeSnapshot(); - + MyAvatar* getMyAvatar() const; - + void checkSkeleton(); - + void initializeAcceptedFiles(); int getRenderAmbientLight() const; - + void displaySide(RenderArgs* renderArgs, Camera& whichCamera, bool selfAvatarOnly = false, bool billboard = false); - + bool importSVOFromURL(const QString& urlString); - + bool nearbyEntitiesAreReadyForPhysics(); int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode); void trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket); - + void resizeEvent(QResizeEvent* size); - + void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); - + void focusOutEvent(QFocusEvent* event); void focusInEvent(QFocusEvent* event); - + void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0); void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0); void mouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID = 0); void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0); - + void touchBeginEvent(QTouchEvent* event); void touchEndEvent(QTouchEvent* event); void touchUpdateEvent(QTouchEvent* event); - + void wheelEvent(QWheelEvent* event); void dropEvent(QDropEvent* event); void dragEnterEvent(QDragEnterEvent* event); - + bool _dependencyManagerIsSetup; @@ -437,8 +437,6 @@ private: float _rotateMirror; float _raiseMirror; - Environment _environment; - QSet _keysPressed; bool _enableProcessOctreeThread; @@ -461,7 +459,7 @@ private: quint64 _lastNackTime; quint64 _lastSendDownstreamAudioStats; - + bool _aboutToQuit; Bookmarks* _bookmarks; @@ -470,9 +468,9 @@ private: QThread _settingsThread; QTimer _settingsTimer; - + GLCanvas* _glWidget{ nullptr }; - + typedef bool (Application::* AcceptURLMethod)(const QString &); static const QHash _acceptedExtensions; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 246a3c5bd1..79257bf6b9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -323,7 +323,6 @@ Menu::Menu() { // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugShadows); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 15cf118192..408ab15d5e 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -154,7 +154,6 @@ namespace MenuOption { const QString AnimDebugDrawPosition= "Debug Draw Position"; const QString Antialiasing = "Antialiasing"; const QString AssetMigration = "ATP Asset Migration"; - const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; const QString AudioNetworkStats = "Audio Network Stats"; const QString AudioNoiseReduction = "Audio Noise Reduction"; @@ -187,8 +186,8 @@ namespace MenuOption { const QString CopyPath = "Copy Path to Clipboard"; const QString CoupleEyelids = "Couple Eyelids"; const QString CrashInterface = "Crash Interface"; - const QString DebugAmbientOcclusion = "Debug Ambient Occlusion"; - const QString DebugShadows = "Debug Shadows"; + const QString DebugShadows = "Shadows"; + const QString DebugAmbientOcclusion = "Ambient Occlusion"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index c8fd5188e2..3305cc4f3b 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -22,11 +22,11 @@ #include #include -#include "../../libraries/render-utils/stars_vert.h" -#include "../../libraries/render-utils/stars_frag.h" +#include +#include -#include "../../libraries/render-utils/standardTransformPNTC_vert.h" -#include "../../libraries/render-utils/starsGrid_frag.h" +#include +#include //static const float TILT = 0.23f; static const float TILT = 0.0f; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 798ddd951c..7fd24ea6b7 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1230,3 +1230,33 @@ void Avatar::updatePalms() { _leftPalmPositionCache.set(getUncachedLeftPalmPosition()); _rightPalmPositionCache.set(getUncachedRightPalmPosition()); } + +void Avatar::setParentID(const QUuid& parentID) { + if (!isMyAvatar()) { + return; + } + bool success; + Transform beforeChangeTransform = getTransform(success); + SpatiallyNestable::setParentID(parentID); + if (success) { + setTransform(beforeChangeTransform, success); + if (!success) { + qDebug() << "Avatar::setParentID failed to reset avatar's location."; + } + } +} + +void Avatar::setParentJointIndex(quint16 parentJointIndex) { + if (!isMyAvatar()) { + return; + } + bool success; + Transform beforeChangeTransform = getTransform(success); + SpatiallyNestable::setParentJointIndex(parentJointIndex); + if (success) { + setTransform(beforeChangeTransform, success); + if (!success) { + qDebug() << "Avatar::setParentJointIndex failed to reset avatar's location."; + } + } +} diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index e40db11800..a9a5e2f81b 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -167,6 +167,12 @@ public: using SpatiallyNestable::setOrientation; virtual void setOrientation(const glm::quat& orientation) override; + // these call through to the SpatiallyNestable versions, but they are here to expose these to javascript. + Q_INVOKABLE virtual const QUuid getParentID() const { return SpatiallyNestable::getParentID(); } + Q_INVOKABLE virtual void setParentID(const QUuid& parentID); + Q_INVOKABLE virtual quint16 getParentJointIndex() const { return SpatiallyNestable::getParentJointIndex(); } + Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex); + // NOT thread safe, must be called on main thread. glm::vec3 getUncachedLeftPalmPosition() const; glm::quat getUncachedLeftPalmRotation() const; diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index b97ec59780..7646f87238 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -30,7 +30,7 @@ public: virtual void updateActionWorker(float deltaTimeStep) override; - QByteArray serialize() const; + QByteArray serialize() const override; virtual void deserialize(QByteArray serializedArguments) override; virtual bool shouldSuppressLocationEdits() override { return _active && !_ownerEntity.expired(); } diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 84f276529a..e8e3bea327 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -134,7 +134,7 @@ glm::vec3 AvatarMotionState::getObjectGravity() const { } // virtual -const QUuid& AvatarMotionState::getObjectID() const { +const QUuid AvatarMotionState::getObjectID() const { return _avatar->getSessionUUID(); } diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 51b4e5627e..715c38186b 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -53,7 +53,7 @@ public: virtual glm::vec3 getObjectAngularVelocity() const override; virtual glm::vec3 getObjectGravity() const override; - virtual const QUuid& getObjectID() const override; + virtual const QUuid getObjectID() const override; virtual QUuid getSimulatorID() const override; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b1688b8f9e..82809cb7a5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -43,7 +43,6 @@ #include "Application.h" #include "devices/Faceshift.h" #include "AvatarManager.h" -#include "Environment.h" #include "Menu.h" #include "MyAvatar.h" #include "Physics.h" diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 1de8f9224c..15da88023b 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -11,7 +11,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -19,71 +22,72 @@ #include "AddressManager.h" #include "Application.h" #include "InterfaceLogging.h" - -#ifdef Q_OS_WIN -static BOOL CALLBACK enumWindowsCallback(HWND hWnd, LPARAM lParam) { - const UINT TIMEOUT = 200; // ms - DWORD_PTR response; - LRESULT result = SendMessageTimeout(hWnd, UWM_IDENTIFY_INSTANCES, 0, 0, SMTO_BLOCK | SMTO_ABORTIFHUNG, TIMEOUT, &response); - if (result == 0) { // Timeout; continue search. - return TRUE; - } - if (response == UWM_IDENTIFY_INSTANCES) { - HWND* target = (HWND*)lParam; - *target = hWnd; - return FALSE; // Found; terminate search. - } - return TRUE; // Not found; continue search. -} -#endif +#include "MainWindow.h" int main(int argc, const char* argv[]) { + disableQtBearerPoll(); // Fixes wifi ping spikes + + QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME"); + + bool instanceMightBeRunning = true; + #ifdef Q_OS_WIN - // Run only one instance of Interface at a time. - HANDLE mutex = CreateMutex(NULL, FALSE, "High Fidelity Interface - " + qgetenv("USERNAME")); - DWORD result = GetLastError(); - if (result == ERROR_ALREADY_EXISTS || result == ERROR_ACCESS_DENIED) { - // Interface is already running. - HWND otherInstance = NULL; - EnumWindows(enumWindowsCallback, (LPARAM)&otherInstance); - if (otherInstance) { - // Show other instance. - SendMessage(otherInstance, UWM_SHOW_APPLICATION, 0, 0); + // Try to create a shared memory block - if it can't be created, there is an instance of + // interface already running. We only do this on Windows for now because of the potential + // for crashed instances to leave behind shared memory instances on unix. + QSharedMemory sharedMemory { applicationName }; + instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly); +#endif - // Send command line --url value to other instance. - if (argc >= 3) { - QStringList arguments; - for (int i = 0; i < argc; i += 1) { - arguments << argv[i]; - } + if (instanceMightBeRunning) { + // Try to connect and send message to existing interface instance + QLocalSocket socket; - QCommandLineParser parser; - QCommandLineOption urlOption("url", "", "value"); - parser.addOption(urlOption); - parser.process(arguments); + socket.connectToServer(applicationName); - if (parser.isSet(urlOption)) { - QUrl url = QUrl(parser.value(urlOption)); - if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) { - QByteArray urlBytes = url.toString().toLatin1(); - const char* urlChars = urlBytes.data(); - COPYDATASTRUCT cds; - cds.cbData = urlBytes.length() + 1; - cds.lpData = (PVOID)urlChars; - SendMessage(otherInstance, WM_COPYDATA, 0, (LPARAM)&cds); + static const int LOCAL_SERVER_TIMEOUT_MS = 500; + + // Try to connect - if we can't connect, interface has probably just gone down + if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) { + + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << argv[i]; + } + + QCommandLineParser parser; + QCommandLineOption urlOption("url", "", "value"); + parser.addOption(urlOption); + parser.process(arguments); + + if (parser.isSet(urlOption)) { + QUrl url = QUrl(parser.value(urlOption)); + if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) { + qDebug() << "Writing URL to local socket"; + socket.write(url.toString().toUtf8()); + if (!socket.waitForBytesWritten(5000)) { + qDebug() << "Error writing URL to local socket"; } } } + + socket.close(); + + qDebug() << "Interface instance appears to be running, exiting"; + + return EXIT_SUCCESS; } - return 0; - } + +#ifdef Q_OS_WIN + return EXIT_SUCCESS; #endif + } QElapsedTimer startupTime; startupTime.start(); - - // Debug option to demonstrate that the client's local time does not - // need to be in sync with any other network node. This forces clock + + // Debug option to demonstrate that the client's local time does not + // need to be in sync with any other network node. This forces clock // skew for the individual client const char* CLOCK_SKEW = "--clockSkew"; const char* clockSkewOption = getCmdOption(argc, argv, CLOCK_SKEW); @@ -102,19 +106,26 @@ int main(int argc, const char* argv[]) { QSettings::setDefaultFormat(QSettings::IniFormat); Application app(argc, const_cast(argv), startupTime); + // Setup local server + QLocalServer server { &app }; + + // We failed to connect to a local server, so we remove any existing servers. + server.removeServer(applicationName); + server.listen(applicationName); + + QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection); + QTranslator translator; translator.load("i18n/interface_en"); app.installTranslator(&translator); - + qCDebug(interfaceapp, "Created QT Application."); exitCode = app.exec(); + server.close(); } Application::shutdownPlugins(); -#ifdef Q_OS_WIN - ReleaseMutex(mutex); -#endif qCDebug(interfaceapp, "Normal exit."); return exitCode; -} +} diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/GlobalServicesScriptingInterface.cpp index 8ca381a607..e764473107 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/GlobalServicesScriptingInterface.cpp @@ -20,6 +20,7 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); + connect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); _downloading = false; QTimer* checkDownloadTimer = new QTimer(this); @@ -36,6 +37,7 @@ GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() { AccountManager& accountManager = AccountManager::getInstance(); disconnect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); disconnect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); + disconnect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); } GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index af8e1c1cda..5fd69128eb 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -216,15 +216,14 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); } // referential state - bool success; - SpatiallyNestablePointer parent = getParentPointer(success); - if (parent && success) { + QUuid parentID = getParentID(); + if (!parentID.isNull()) { setAtBit(bitItems, HAS_REFERENTIAL); } *destinationBuffer++ = bitItems; - if (parent) { - QByteArray referentialAsBytes = parent->getID().toRfc4122(); + if (!parentID.isNull()) { + QByteArray referentialAsBytes = parentID.toRfc4122(); memcpy(destinationBuffer, referentialAsBytes.data(), referentialAsBytes.size()); destinationBuffer += referentialAsBytes.size(); memcpy(destinationBuffer, &_parentJointIndex, sizeof(_parentJointIndex)); @@ -492,10 +491,12 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // avatar's SkeletonModel might fall into the CPU expensive part of Model::updateClusterMatrices() when otherwise it // would not have required it. However, we know we can update many simultaneously animating avatars, and most // avatars will be moving constantly anyway, so I don't think we need to worry. - if (getBodyYaw() != yaw || getBodyPitch() != pitch || getBodyRoll() != roll) { + glm::quat currentOrientation = getLocalOrientation(); + glm::vec3 newEulerAngles(pitch, yaw, roll); + glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); + if (currentOrientation != newOrientation) { _hasNewJointRotations = true; - glm::vec3 eulerAngles(pitch, yaw, roll); - setLocalOrientation(glm::quat(glm::radians(eulerAngles))); + setLocalOrientation(newOrientation); } // scale diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index c443af7b70..3b8214f226 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -177,7 +177,7 @@ public: virtual bool isMyAvatar() const { return false; } - const QUuid& getSessionUUID() const { return getID(); } + const QUuid getSessionUUID() const { return getID(); } glm::vec3 getHandPosition() const; void setHandPosition(const glm::vec3& handPosition); diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 4c804f2e7b..bd256578d8 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -23,8 +23,9 @@ const int SOCKET_ERROR_EXIT_CODE = 2; const int SOCKET_CHECK_INTERVAL_IN_MS = 30000; -HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : +HTTPManager::HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : QTcpServer(parent), + _listenAddress(listenAddress), _documentRoot(documentRoot), _requestHandler(requestHandler), _port(port) @@ -178,18 +179,20 @@ void HTTPManager::isTcpServerListening() { bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - if (listen(QHostAddress::AnyIPv4, _port)) { + if (listen(_listenAddress, _port)) { qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); return true; } else { - qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; - QMetaObject::invokeMethod(this, "queuedExit", Qt::QueuedConnection); - + QString errorMessage = "Failed to open HTTP server socket: " + errorString() + ", can't continue"; + QMetaObject::invokeMethod(this, "queuedExit", Qt::QueuedConnection, Q_ARG(QString, errorMessage)); return false; } } -void HTTPManager::queuedExit() { +void HTTPManager::queuedExit(QString errorMessage) { + if (!errorMessage.isEmpty()) { + qCCritical(embeddedwebserver) << qPrintable(errorMessage); + } QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); } diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 03498fbe8d..c560c43a5e 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -33,13 +33,13 @@ class HTTPManager : public QTcpServer, public HTTPRequestHandler { Q_OBJECT public: /// Initializes the manager. - HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0); + HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); private slots: void isTcpServerListening(); - void queuedExit(); + void queuedExit(QString errorMessage); private: bool bindSocket(); @@ -49,6 +49,7 @@ protected: virtual void incomingConnection(qintptr socketDescriptor); virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url); + QHostAddress _listenAddress; QString _documentRoot; HTTPRequestHandler* _requestHandler; QTimer* _isListeningTimer; diff --git a/libraries/embedded-webserver/src/HTTPSManager.cpp b/libraries/embedded-webserver/src/HTTPSManager.cpp index a745d7605e..ee61f15457 100644 --- a/libraries/embedded-webserver/src/HTTPSManager.cpp +++ b/libraries/embedded-webserver/src/HTTPSManager.cpp @@ -15,9 +15,9 @@ #include "HTTPSManager.h" -HTTPSManager::HTTPSManager(quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey, +HTTPSManager::HTTPSManager(QHostAddress listenAddress, quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey, const QString& documentRoot, HTTPSRequestHandler* requestHandler, QObject* parent) : - HTTPManager(port, documentRoot, requestHandler, parent), + HTTPManager(listenAddress, port, documentRoot, requestHandler, parent), _certificate(certificate), _privateKey(privateKey), _sslRequestHandler(requestHandler) diff --git a/libraries/embedded-webserver/src/HTTPSManager.h b/libraries/embedded-webserver/src/HTTPSManager.h index 66c0c76d0b..2d3cc9ed62 100644 --- a/libraries/embedded-webserver/src/HTTPSManager.h +++ b/libraries/embedded-webserver/src/HTTPSManager.h @@ -26,7 +26,8 @@ public: class HTTPSManager : public HTTPManager, public HTTPSRequestHandler { Q_OBJECT public: - HTTPSManager(quint16 port, + HTTPSManager(QHostAddress listenAddress, + quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey, const QString& documentRoot, @@ -35,12 +36,12 @@ public: void setCertificate(const QSslCertificate& certificate) { _certificate = certificate; } void setPrivateKey(const QSslKey& privateKey) { _privateKey = privateKey; } - bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); - bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; + bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override; protected: - void incomingConnection(qintptr socketDescriptor); - bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url); + void incomingConnection(qintptr socketDescriptor) override; + bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) override; private: QSslCertificate _certificate; QSslKey _privateKey; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index a2c1cf27c2..0ea35e6c5a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -277,8 +277,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrendOverrideEnvironmentData(); - skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); // let the application atmosphere through + skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); // let the application background through return; // Early exit } @@ -308,28 +307,6 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrsetDay(zone->getStageProperties().calculateDay()); switch (zone->getBackgroundMode()) { - case BACKGROUND_MODE_ATMOSPHERE: { - EnvironmentData data = zone->getEnvironmentData(); - glm::vec3 keyLightDirection = sceneKeyLight->getDirection(); - glm::vec3 inverseKeyLightDirection = keyLightDirection * -1.0f; - - // NOTE: is this right? It seems like the "sun" should be based on the center of the - // atmosphere, not where the camera is. - glm::vec3 keyLightLocation = _viewState->getAvatarPosition() + - (inverseKeyLightDirection * data.getAtmosphereOuterRadius()); - - data.setSunLocation(keyLightLocation); - - const float KEY_LIGHT_INTENSITY_TO_SUN_BRIGHTNESS_RATIO = 20.0f; - float sunBrightness = sceneKeyLight->getIntensity() * KEY_LIGHT_INTENSITY_TO_SUN_BRIGHTNESS_RATIO; - data.setSunBrightness(sunBrightness); - - _viewState->overrideEnvironmentData(data); - skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); - _pendingSkyboxTexture = false; - _skyboxTexture.clear(); - break; - } case BACKGROUND_MODE_SKYBOX: { auto skybox = std::dynamic_pointer_cast(skyStage->getSkybox()); skybox->setColor(zone->getSkyboxProperties().getColorVec3()); @@ -360,13 +337,13 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrendOverrideEnvironmentData(); skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX); break; } + case BACKGROUND_MODE_INHERIT: - _viewState->endOverrideEnvironmentData(); - skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); // let the application atmosphere through + default: + skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); // let the application background through _pendingSkyboxTexture = false; _skyboxTexture.clear(); break; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 212b71759f..d7d8d65e3a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -39,7 +39,7 @@ public: }; namespace render { - template <> const ItemKey payloadGetKey(const RenderableEntityItemProxy::Pointer& payload); + template <> const ItemKey payloadGetKey(const RenderableEntityItemProxy::Pointer& payload); template <> const Item::Bound payloadGetBound(const RenderableEntityItemProxy::Pointer& payload); template <> void payloadRender(const RenderableEntityItemProxy::Pointer& payload, RenderArgs* args); } @@ -73,8 +73,8 @@ private: #define SIMPLE_RENDERABLE() \ public: \ - virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { return _renderHelper.addToScene(self, scene, pendingChanges); } \ - virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { _renderHelper.removeFromScene(self, scene, pendingChanges); } \ + virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override { return _renderHelper.addToScene(self, scene, pendingChanges); } \ + virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override { _renderHelper.removeFromScene(self, scene, pendingChanges); } \ private: \ SimpleRenderableEntityItem _renderHelper; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index d320610d83..dfde97c407 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -27,19 +27,19 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); static void createPipeline(); RenderablePolyLineEntityItem(const EntityItemID& entityItemID); - - virtual void render(RenderArgs* args); + + virtual void render(RenderArgs* args) override; virtual void update(const quint64& now) override; - virtual bool needsToCallUpdate() const { return true; }; - + virtual bool needsToCallUpdate() const override { return true; }; + SIMPLE_RENDERABLE(); - + NetworkTexturePointer _texture; static gpu::PipelinePointer _pipeline; static gpu::Stream::FormatPointer _format; static int32_t PAINTSTROKE_GPU_SLOT; - + protected: void updateGeometry(); void updateVertices(); diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index 26e90f5d41..7bea5c6088 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) -link_hifi_libraries(avatars shared audio octree gpu model fbx networking animation environment) +link_hifi_libraries(avatars shared audio octree gpu model fbx networking animation) target_bullet() diff --git a/libraries/entities/src/AtmospherePropertyGroup.cpp b/libraries/entities/src/AtmospherePropertyGroup.cpp deleted file mode 100644 index 8e7a5a5262..0000000000 --- a/libraries/entities/src/AtmospherePropertyGroup.cpp +++ /dev/null @@ -1,230 +0,0 @@ -// -// AtmospherePropertyGroup.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 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 - -#include "AtmospherePropertyGroup.h" -#include "EntityItemProperties.h" -#include "EntityItemPropertiesMacros.h" - -const glm::vec3 AtmospherePropertyGroup::DEFAULT_CENTER = glm::vec3(0.0f, -1000.0f, 0.0f); -const float AtmospherePropertyGroup::DEFAULT_INNER_RADIUS = 1000.0f; -const float AtmospherePropertyGroup::DEFAULT_OUTER_RADIUS = 1025.0f; -const float AtmospherePropertyGroup::DEFAULT_RAYLEIGH_SCATTERING = 0.0025f; -const float AtmospherePropertyGroup::DEFAULT_MIE_SCATTERING = 0.0010f; -const glm::vec3 AtmospherePropertyGroup::DEFAULT_SCATTERING_WAVELENGTHS = glm::vec3(0.650f, 0.570f, 0.475f); -const bool AtmospherePropertyGroup::DEFAULT_HAS_STARS = true; - -void AtmospherePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_CENTER, Atmosphere, atmosphere, Center, center); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_INNER_RADIUS, Atmosphere, atmosphere, InnerRadius, innerRadius); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_OUTER_RADIUS, Atmosphere, atmosphere, OuterRadius, outerRadius); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_MIE_SCATTERING, Atmosphere, atmosphere, MieScattering, mieScattering); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, Atmosphere, atmosphere, RayleighScattering, rayleighScattering); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, Atmosphere, atmosphere, ScatteringWavelengths, scatteringWavelengths); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_HAS_STARS, Atmosphere, atmosphere, HasStars, hasStars); -} - -void AtmospherePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, center, glmVec3, setCenter); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, innerRadius, float, setInnerRadius); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, outerRadius, float, setOuterRadius); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, mieScattering, float, setMieScattering); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, rayleighScattering, float, setRayleighScattering); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, scatteringWavelengths, glmVec3, setScatteringWavelengths); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(atmosphere, hasStars, bool, setHasStars); -} - -void AtmospherePropertyGroup::debugDump() const { - qDebug() << " AtmospherePropertyGroup: ---------------------------------------------"; - qDebug() << " Center:" << getCenter() << " has changed:" << centerChanged(); - qDebug() << " Inner Radius:" << getInnerRadius() << " has changed:" << innerRadiusChanged(); - qDebug() << " Outer Radius:" << getOuterRadius() << " has changed:" << outerRadiusChanged(); - qDebug() << " Mie Scattering:" << getMieScattering() << " has changed:" << mieScatteringChanged(); - qDebug() << " Rayleigh Scattering:" << getRayleighScattering() << " has changed:" << rayleighScatteringChanged(); - qDebug() << " Scattering Wavelengths:" << getScatteringWavelengths() << " has changed:" << scatteringWavelengthsChanged(); - qDebug() << " Has Stars:" << getHasStars() << " has changed:" << hasStarsChanged(); -} - -void AtmospherePropertyGroup::listChangedProperties(QList& out) { - if (centerChanged()) { - out << "center"; - } - if (innerRadiusChanged()) { - out << "innerRadius"; - } - if (outerRadiusChanged()) { - out << "outerRadius"; - } - if (mieScatteringChanged()) { - out << "mieScattering"; - } - if (rayleighScatteringChanged()) { - out << "rayleighScattering"; - } - if (scatteringWavelengthsChanged()) { - out << "scatteringWavelengths"; - } - if (hasStarsChanged()) { - out << "hasStars"; - } -} - -bool AtmospherePropertyGroup::appendToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_CENTER, getCenter()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_INNER_RADIUS, getInnerRadius()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_OUTER_RADIUS, getOuterRadius()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_MIE_SCATTERING, getMieScattering()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, getRayleighScattering()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, getScatteringWavelengths()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_HAS_STARS, getHasStars()); - - return true; -} - - -bool AtmospherePropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { - - int bytesRead = 0; - bool overwriteLocalData = true; - bool somethingChanged = false; - - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_CENTER, glm::vec3, setCenter); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_INNER_RADIUS, float, setInnerRadius); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_OUTER_RADIUS, float, setOuterRadius); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_MIE_SCATTERING, float, setMieScattering); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, float, setRayleighScattering); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, glm::vec3, setScatteringWavelengths); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_HAS_STARS, bool, setHasStars); - - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_CENTER, Center); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_INNER_RADIUS, InnerRadius); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_OUTER_RADIUS, OuterRadius); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_MIE_SCATTERING, MieScattering); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, RayleighScattering); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, ScatteringWavelengths); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_HAS_STARS, HasStars); - - processedBytes += bytesRead; - - Q_UNUSED(somethingChanged); - - return true; -} - -void AtmospherePropertyGroup::markAllChanged() { - _centerChanged = true; - _innerRadiusChanged = true; - _outerRadiusChanged = true; - _mieScatteringChanged = true; - _rayleighScatteringChanged = true; - _scatteringWavelengthsChanged = true; - _hasStarsChanged = true; -} - -EntityPropertyFlags AtmospherePropertyGroup::getChangedProperties() const { - EntityPropertyFlags changedProperties; - - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_CENTER, center); - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_INNER_RADIUS, innerRadius); - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_OUTER_RADIUS, outerRadius); - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_MIE_SCATTERING, mieScattering); - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, rayleighScattering); - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, scatteringWavelengths); - CHECK_PROPERTY_CHANGE(PROP_ATMOSPHERE_HAS_STARS, hasStars); - - return changedProperties; -} - -void AtmospherePropertyGroup::getProperties(EntityItemProperties& properties) const { - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, Center, getCenter); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, InnerRadius, getInnerRadius); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, OuterRadius, getOuterRadius); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, MieScattering, getMieScattering); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, MieScattering, getMieScattering); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, RayleighScattering, getRayleighScattering); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, ScatteringWavelengths, getScatteringWavelengths); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Atmosphere, HasStars, getHasStars); -} - -bool AtmospherePropertyGroup::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, Center, center, setCenter); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, InnerRadius, innerRadius, setInnerRadius); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, OuterRadius, outerRadius, setOuterRadius); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, MieScattering, mieScattering, setMieScattering); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, RayleighScattering, rayleighScattering, setRayleighScattering); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, ScatteringWavelengths, scatteringWavelengths, setScatteringWavelengths); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Atmosphere, HasStars, hasStars, setHasStars); - - return somethingChanged; -} - -EntityPropertyFlags AtmospherePropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties; - - requestedProperties += PROP_ATMOSPHERE_CENTER; - requestedProperties += PROP_ATMOSPHERE_INNER_RADIUS; - requestedProperties += PROP_ATMOSPHERE_OUTER_RADIUS; - requestedProperties += PROP_ATMOSPHERE_MIE_SCATTERING; - requestedProperties += PROP_ATMOSPHERE_RAYLEIGH_SCATTERING; - requestedProperties += PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS; - requestedProperties += PROP_ATMOSPHERE_HAS_STARS; - - return requestedProperties; -} - -void AtmospherePropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_CENTER, getCenter()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_INNER_RADIUS, getInnerRadius()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_OUTER_RADIUS, getOuterRadius()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_MIE_SCATTERING, getMieScattering()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, getRayleighScattering()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, getScatteringWavelengths()); - APPEND_ENTITY_PROPERTY(PROP_ATMOSPHERE_HAS_STARS, getHasStars()); -} - -int AtmospherePropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_CENTER, glm::vec3, setCenter); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_INNER_RADIUS, float, setInnerRadius); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_OUTER_RADIUS, float, setOuterRadius); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_MIE_SCATTERING, float, setMieScattering); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, float, setRayleighScattering); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, glm::vec3, setScatteringWavelengths); - READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_HAS_STARS, bool, setHasStars); - - return bytesRead; -} diff --git a/libraries/entities/src/AtmospherePropertyGroup.h b/libraries/entities/src/AtmospherePropertyGroup.h deleted file mode 100644 index 1d43c848b8..0000000000 --- a/libraries/entities/src/AtmospherePropertyGroup.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// AtmospherePropertyGroup.h -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 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_AtmospherePropertyGroup_h -#define hifi_AtmospherePropertyGroup_h - -#include - -#include "PropertyGroup.h" -#include "EntityItemPropertiesMacros.h" - -class EntityItemProperties; -class EncodeBitstreamParams; -class OctreePacketData; -class EntityTreeElementExtraEncodeData; -class ReadBitstreamToTreeParams; - -#include -#include - - -/* - -#include - -#include -#include -#include - -#include -#include // for SittingPoint -#include -#include -#include - -#include "EntityItemID.h" -#include "AtmospherePropertyGroupMacros.h" -#include "EntityTypes.h" -*/ - - -class AtmospherePropertyGroup : public PropertyGroup { -public: - // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings); - virtual void debugDump() const; - virtual void listChangedProperties(QList& out); - - virtual bool appendToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes); - virtual void markAllChanged(); - virtual EntityPropertyFlags getChangedProperties() const; - - // EntityItem related helpers - // methods for getting/setting all properties of an entity - virtual void getProperties(EntityItemProperties& propertiesOut) const; - - /// returns true if something changed - virtual bool setProperties(const EntityItemProperties& properties); - - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged); - - - static const glm::vec3 DEFAULT_CENTER; - static const float DEFAULT_INNER_RADIUS; - static const float DEFAULT_OUTER_RADIUS; - static const float DEFAULT_RAYLEIGH_SCATTERING; - static const float DEFAULT_MIE_SCATTERING; - static const glm::vec3 DEFAULT_SCATTERING_WAVELENGTHS; - static const bool DEFAULT_HAS_STARS; - - DEFINE_PROPERTY_REF(PROP_ATMOSPHERE_CENTER, Center, center, glm::vec3, DEFAULT_CENTER); - DEFINE_PROPERTY(PROP_ATMOSPHERE_INNER_RADIUS, InnerRadius, innerRadius, float, DEFAULT_INNER_RADIUS); - DEFINE_PROPERTY(PROP_ATMOSPHERE_OUTER_RADIUS, OuterRadius, outerRadius, float, DEFAULT_OUTER_RADIUS); - DEFINE_PROPERTY(PROP_ATMOSPHERE_MIE_SCATTERING, MieScattering, mieScattering, float, DEFAULT_MIE_SCATTERING); - DEFINE_PROPERTY(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, RayleighScattering, rayleighScattering, float, DEFAULT_RAYLEIGH_SCATTERING); - DEFINE_PROPERTY_REF(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, ScatteringWavelengths, scatteringWavelengths, glm::vec3, DEFAULT_SCATTERING_WAVELENGTHS); - DEFINE_PROPERTY(PROP_ATMOSPHERE_HAS_STARS, HasStars, hasStars, bool, DEFAULT_HAS_STARS); -}; - -#endif // hifi_AtmospherePropertyGroup_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0535621c85..6644f28139 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -25,7 +25,6 @@ #include "PolyLineEntityItem.h" AnimationPropertyGroup EntityItemProperties::_staticAnimation; -AtmospherePropertyGroup EntityItemProperties::_staticAtmosphere; SkyboxPropertyGroup EntityItemProperties::_staticSkybox; StagePropertyGroup EntityItemProperties::_staticStage; KeyLightPropertyGroup EntityItemProperties::_staticKeyLight; @@ -79,7 +78,6 @@ void EntityItemProperties::debugDump() const { qCDebug(entities) << " _compoundShapeURL=" << _compoundShapeURL; getAnimation().debugDump(); - getAtmosphere().debugDump(); getSkybox().debugDump(); getKeyLight().debugDump(); @@ -186,39 +184,26 @@ void EntityItemProperties::setShapeTypeFromString(const QString& shapeName) { } } -const char* backgroundModeNames[] = {"inherit", "atmosphere", "skybox" }; - -QHash stringToBackgroundModeLookup; - -void addBackgroundMode(BackgroundMode type) { - stringToBackgroundModeLookup[backgroundModeNames[type]] = type; -} - -void buildStringToBackgroundModeLookup() { - addBackgroundMode(BACKGROUND_MODE_INHERIT); - addBackgroundMode(BACKGROUND_MODE_ATMOSPHERE); - addBackgroundMode(BACKGROUND_MODE_SKYBOX); -} +using BackgroundPair = std::pair; +const std::array BACKGROUND_MODES = { + BackgroundPair { BACKGROUND_MODE_INHERIT, { "inherit" } }, + BackgroundPair { BACKGROUND_MODE_SKYBOX, { "skybox" } } +}; QString EntityItemProperties::getBackgroundModeAsString() const { - if (_backgroundMode < sizeof(backgroundModeNames) / sizeof(char *)) - return QString(backgroundModeNames[_backgroundMode]); - return QString(backgroundModeNames[BACKGROUND_MODE_INHERIT]); + return BACKGROUND_MODES[_backgroundMode].second; } QString EntityItemProperties::getBackgroundModeString(BackgroundMode mode) { - if (mode < sizeof(backgroundModeNames) / sizeof(char *)) - return QString(backgroundModeNames[mode]); - return QString(backgroundModeNames[BACKGROUND_MODE_INHERIT]); + return BACKGROUND_MODES[mode].second; } void EntityItemProperties::setBackgroundModeFromString(const QString& backgroundMode) { - if (stringToBackgroundModeLookup.empty()) { - buildStringToBackgroundModeLookup(); - } - auto backgroundModeItr = stringToBackgroundModeLookup.find(backgroundMode.toLower()); - if (backgroundModeItr != stringToBackgroundModeLookup.end()) { - _backgroundMode = backgroundModeItr.value(); + auto result = std::find_if(BACKGROUND_MODES.begin(), BACKGROUND_MODES.end(), [&](const BackgroundPair& pair) { + return (pair.second == backgroundMode); + }); + if (result != BACKGROUND_MODES.end()) { + _backgroundMode = result->first; _backgroundModeChanged = true; } } @@ -326,7 +311,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); - changedProperties += _atmosphere.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); changedProperties += _stage.getChangedProperties(); @@ -374,7 +358,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISIONLESS, collisionless); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISIONLESS, collisionless, ignoreForCollisions, getCollisionless()); // legacy support - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISION_MASK, collisionMask, getCollisionMaskAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_MASK, collisionMask); + COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISION_MASK, collisionMask, collidesWith, getCollisionMaskAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DYNAMIC, dynamic); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_DYNAMIC, dynamic, collisionsWillMove, getDynamic()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href); @@ -474,7 +459,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_MODE, backgroundMode, getBackgroundModeAsString()); _stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - _atmosphere.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } @@ -602,7 +586,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(localRenderAlpha, float, setLocalRenderAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionless, bool, setCollisionless); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ignoreForCollisions, bool, setCollisionless, getCollisionless); // legacy support - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(collisionMask, CollisionMask); + COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionMask, uint8_t, setCollisionMask); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(collidesWith, CollisionMask); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(collisionsWillMove, bool, setDynamic, getDynamic); // legacy support COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic); COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight); @@ -666,7 +651,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool _animation.copyFromScriptValue(object, _defaultSettings); _keyLight.copyFromScriptValue(object, _defaultSettings); - _atmosphere.copyFromScriptValue(object, _defaultSettings); _skybox.copyFromScriptValue(object, _defaultSettings); _stage.copyFromScriptValue(object, _defaultSettings); @@ -767,7 +751,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float); ADD_PROPERTY_TO_MAP(PROP_COLLISIONLESS, Collisionless, collisionless, bool); ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, ignoreForCollisions, unused); // legacy support - ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint8_t); + ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, unused, collisionMask, unused); + ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, unused, collidesWith, unused); ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, collisionsWillMove, unused); // legacy support ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused); ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool); @@ -851,14 +836,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_CENTER, Atmosphere, atmosphere, Center, center); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_INNER_RADIUS, Atmosphere, atmosphere, InnerRadius, innerRadius); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_OUTER_RADIUS, Atmosphere, atmosphere, OuterRadius, outerRadius); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_MIE_SCATTERING, Atmosphere, atmosphere, MieScattering, mieScattering); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, Atmosphere, atmosphere, RayleighScattering, rayleighScattering); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, Atmosphere, atmosphere, ScatteringWavelengths, scatteringWavelengths); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_HAS_STARS, Atmosphere, atmosphere, HasStars, hasStars); - ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color); ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_URL, Skybox, skybox, URL, url); @@ -876,6 +853,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue }); if (object.isString()) { + // TODO: figure out how to do this without a double lookup in the map if (_propertyStringsToEnums.contains(object.toString())) { flags << _propertyStringsToEnums[object.toString()]; } @@ -883,6 +861,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue quint32 length = object.property("length").toInt32(); for (quint32 i = 0; i < length; i++) { QString propertyName = object.property(i).toString(); + // TODO: figure out how to do this without a double lookup in the map if (_propertyStringsToEnums.contains(propertyName)) { flags << _propertyStringsToEnums[propertyName]; } @@ -1105,9 +1084,6 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)properties.getBackgroundMode()); - _staticAtmosphere.setProperties(properties); - _staticAtmosphere.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - _staticSkybox.setProperties(properties); _staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); } @@ -1392,7 +1368,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); - properties.getAtmosphere().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); } @@ -1544,7 +1519,6 @@ void EntityItemProperties::markAllChanged() { _backgroundModeChanged = true; _animation.markAllChanged(); - _atmosphere.markAllChanged(); _skybox.markAllChanged(); _stage.markAllChanged(); @@ -1905,7 +1879,6 @@ QList EntityItemProperties::listChangedProperties() { getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); - getAtmosphere().listChangedProperties(out); getSkybox().listChangedProperties(out); getStage().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 0f0bab5fdd..ff288fbe95 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -30,7 +30,6 @@ #include #include "AnimationPropertyGroup.h" -#include "AtmospherePropertyGroup.h" #include "EntityItemID.h" #include "EntityItemPropertiesDefaults.h" #include "EntityItemPropertiesMacros.h" @@ -171,7 +170,6 @@ public: DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); DEFINE_PROPERTY_REF_ENUM(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode, BACKGROUND_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); - DEFINE_PROPERTY_GROUP(Atmosphere, atmosphere, AtmospherePropertyGroup); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); @@ -419,7 +417,6 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslations, jointTranslations, ""); properties.getAnimation().debugDump(); - properties.getAtmosphere().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index c1ac9f454b..f386a474d8 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -191,9 +191,9 @@ typedef QVector qVectorBool; typedef QVector qVectorFloat; inline float float_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } inline quint64 quint64_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } -inline quint32 quint32_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline quint32 quint32_convertFromScriptValue(const QScriptValue& v, bool& isValid) { // Use QString::toUInt() so that isValid is set to false if the number is outside the quint32 range. - return v.toString().toUInt(&isValid); + return v.toString().toUInt(&isValid); } inline quint16 quint16_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline uint16_t uint16_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index ac0a65aa27..b60fc6174c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -197,13 +197,6 @@ enum EntityPropertyList { PROP_STAGE_DAY = PROP_LINEAR_ATTENUATION_UNUSED, PROP_STAGE_HOUR = PROP_QUADRATIC_ATTENUATION_UNUSED, PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX, - PROP_ATMOSPHERE_CENTER = PROP_MAX_PARTICLES, - PROP_ATMOSPHERE_INNER_RADIUS = PROP_LIFESPAN, - PROP_ATMOSPHERE_OUTER_RADIUS = PROP_EMIT_RATE, - PROP_ATMOSPHERE_MIE_SCATTERING = PROP_EMIT_SPEED, - PROP_ATMOSPHERE_RAYLEIGH_SCATTERING = PROP_EMIT_STRENGTH, - PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS = PROP_EMIT_ACCELERATION, - PROP_ATMOSPHERE_HAS_STARS = PROP_PARTICLE_RADIUS, PROP_BACKGROUND_MODE = PROP_MODEL_URL, PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2a8d3352b0..34dd809510 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -218,7 +218,13 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); - UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, properties.getQueryAACube()); + AACube newQueryAACube; + if (properties.queryAACubeChanged()) { + newQueryAACube = properties.getQueryAACube(); + } else { + newQueryAACube = entity->getQueryAACube(); + } + UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube); recurseTreeWithOperator(&theOperator); entity->setProperties(properties); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index b7e4d11d7b..36f29c1a05 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -309,29 +309,29 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData } // Now check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of it's position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen // before we consider including it. if (includeThisEntity) { AABox entityBounds = entity->getAABox(success); - if (success) { - auto renderAccuracy = params.viewFrustum->calculateRenderAccuracy(entityBounds, - params.octreeElementSizeScale, params.boundaryLevelAdjust); + if (!success) { + // if this entity is a child of an avatar, the entity-server wont be able to determine its + // AABox. If this happens, fall back to the queryAACube. + entityBounds = AABox(entityCube); + } + auto renderAccuracy = params.viewFrustum->calculateRenderAccuracy(entityBounds, + params.octreeElementSizeScale, + params.boundaryLevelAdjust); + if (renderAccuracy <= 0.0f) { + includeThisEntity = false; // too small, don't include it - if (renderAccuracy <= 0.0f) { - includeThisEntity = false; // too small, don't include it - - #ifdef WANT_LOD_DEBUGGING - qDebug() << "skipping entity - TOO SMALL - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "..bounds:" << entityBounds << "\n" - << "....cell:" << getAACube(); - #endif - - } - } else { - includeThisEntity = false; // couldn't get box, don't include it + #ifdef WANT_LOD_DEBUGGING + qDebug() << "skipping entity - TOO SMALL - \n" + << "......id:" << entity->getID() << "\n" + << "....name:" << entity->getName() << "\n" + << "..bounds:" << entityBounds << "\n" + << "....cell:" << getAACube(); + #endif } } } diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 41f722fe36..6aabef5cc0 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -41,26 +41,6 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _backgroundMode = BACKGROUND_MODE_INHERIT; } - -EnvironmentData ZoneEntityItem::getEnvironmentData() const { - EnvironmentData result; - - result.setAtmosphereCenter(_atmosphereProperties.getCenter()); - result.setAtmosphereInnerRadius(_atmosphereProperties.getInnerRadius()); - result.setAtmosphereOuterRadius(_atmosphereProperties.getOuterRadius()); - result.setRayleighScattering(_atmosphereProperties.getRayleighScattering()); - result.setMieScattering(_atmosphereProperties.getMieScattering()); - result.setScatteringWavelengths(_atmosphereProperties.getScatteringWavelengths()); - result.setHasStars(_atmosphereProperties.getHasStars()); - - // NOTE: The sunLocation and SunBrightness will be overwritten in the EntityTreeRenderer to use the - // keyLight details from the scene interface - //result.setSunLocation(1000, 900, 1000)); - //result.setSunBrightness(20.0f); - - return result; -} - EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class @@ -73,7 +53,6 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundMode, getBackgroundMode); - _atmosphereProperties.getProperties(properties); _skyboxProperties.getProperties(properties); return properties; @@ -91,10 +70,9 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode); - bool somethingChangedInAtmosphere = _atmosphereProperties.setProperties(properties); bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties); - somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInAtmosphere || somethingChangedInSkybox; + somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInSkybox; if (somethingChanged) { bool wantDebug = false; @@ -133,12 +111,6 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); - int bytesFromAtmosphere = _atmosphereProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, somethingChanged); - - bytesRead += bytesFromAtmosphere; - dataAt += bytesFromAtmosphere; - int bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); bytesRead += bytesFromSkybox; @@ -158,7 +130,6 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_BACKGROUND_MODE; requestedProperties += _stageProperties.getEntityProperties(params); - requestedProperties += _atmosphereProperties.getEntityProperties(params); requestedProperties += _skyboxProperties.getEntityProperties(params); return requestedProperties; @@ -185,9 +156,6 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)getBackgroundMode()); // could this be a uint16?? - _atmosphereProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -203,7 +171,6 @@ void ZoneEntityItem::debugDump() const { _keyLightProperties.debugDump(); _stageProperties.debugDump(); - _atmosphereProperties.debugDump(); _skyboxProperties.debugDump(); } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index bf323248c0..0326a0f441 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -12,10 +12,7 @@ #ifndef hifi_ZoneEntityItem_h #define hifi_ZoneEntityItem_h -#include - #include "KeyLightPropertyGroup.h" -#include "AtmospherePropertyGroup.h" #include "EntityItem.h" #include "EntityTree.h" #include "SkyboxPropertyGroup.h" @@ -70,8 +67,6 @@ public: void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; } BackgroundMode getBackgroundMode() const { return _backgroundMode; } - EnvironmentData getEnvironmentData() const; - const AtmospherePropertyGroup& getAtmosphereProperties() const { return _atmosphereProperties; } const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; } const StagePropertyGroup& getStageProperties() const { return _stageProperties; } @@ -95,7 +90,6 @@ protected: BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT; StagePropertyGroup _stageProperties; - AtmospherePropertyGroup _atmosphereProperties; SkyboxPropertyGroup _skyboxProperties; static bool _drawZoneBoundaries; diff --git a/libraries/environment/CMakeLists.txt b/libraries/environment/CMakeLists.txt deleted file mode 100644 index 11538a8f2b..0000000000 --- a/libraries/environment/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -set(TARGET_NAME environment) -setup_hifi_library() -link_hifi_libraries(shared networking) diff --git a/libraries/environment/src/EnvironmentData.cpp b/libraries/environment/src/EnvironmentData.cpp deleted file mode 100644 index 7a0bd29bc0..0000000000 --- a/libraries/environment/src/EnvironmentData.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// -// EnvironmentData.cpp -// libraries/environment/src -// -// Created by Andrzej Kapolka on 5/6/13. -// Copyright 2013 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 - -#include "EnvironmentData.h" - -// initial values from Sean O'Neil's GPU Gems entry (http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html), -// GameEngine.cpp -EnvironmentData::EnvironmentData(int id) : - _id(id), - _flat(true), - _gravity(0.0f), - _atmosphereCenter(0, -1000, 0), - _atmosphereInnerRadius(1000.0), - _atmosphereOuterRadius(1025.0), - _rayleighScattering(0.0025f), - _mieScattering(0.0010f), - _scatteringWavelengths(0.650f, 0.570f, 0.475f), - _sunLocation(1000, 900, 1000), - _sunBrightness(20.0f), - _hasStars(true) { -} - -glm::vec3 EnvironmentData::getAtmosphereCenter(const glm::vec3& cameraPosition) const { - return _atmosphereCenter + (_flat ? glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z) : glm::vec3()); -} - -glm::vec3 EnvironmentData::getSunLocation(const glm::vec3& cameraPosition) const { - return _sunLocation; -} - -size_t EnvironmentData::getBroadcastData(unsigned char* destinationBuffer) const { - unsigned char* bufferStart = destinationBuffer; - - memcpy(destinationBuffer, &_id, sizeof(_id)); - destinationBuffer += sizeof(_id); - - memcpy(destinationBuffer, &_flat, sizeof(_flat)); - destinationBuffer += sizeof(_flat); - - memcpy(destinationBuffer, &_gravity, sizeof(_gravity)); - destinationBuffer += sizeof(_gravity); - - memcpy(destinationBuffer, &_atmosphereCenter, sizeof(_atmosphereCenter)); - destinationBuffer += sizeof(_atmosphereCenter); - - memcpy(destinationBuffer, &_atmosphereInnerRadius, sizeof(_atmosphereInnerRadius)); - destinationBuffer += sizeof(_atmosphereInnerRadius); - - memcpy(destinationBuffer, &_atmosphereOuterRadius, sizeof(_atmosphereOuterRadius)); - destinationBuffer += sizeof(_atmosphereOuterRadius); - - memcpy(destinationBuffer, &_rayleighScattering, sizeof(_rayleighScattering)); - destinationBuffer += sizeof(_rayleighScattering); - - memcpy(destinationBuffer, &_mieScattering, sizeof(_mieScattering)); - destinationBuffer += sizeof(_mieScattering); - - memcpy(destinationBuffer, &_scatteringWavelengths, sizeof(_scatteringWavelengths)); - destinationBuffer += sizeof(_scatteringWavelengths); - - memcpy(destinationBuffer, &_sunLocation, sizeof(_sunLocation)); - destinationBuffer += sizeof(_sunLocation); - - memcpy(destinationBuffer, &_sunBrightness, sizeof(_sunBrightness)); - destinationBuffer += sizeof(_sunBrightness); - - return destinationBuffer - bufferStart; -} - -size_t EnvironmentData::parseData(const unsigned char* sourceBuffer, int numBytes) { - const unsigned char* startPosition = sourceBuffer; - - memcpy(&_id, sourceBuffer, sizeof(_id)); - sourceBuffer += sizeof(_id); - - memcpy(&_flat, sourceBuffer, sizeof(_flat)); - sourceBuffer += sizeof(_flat); - - memcpy(&_gravity, sourceBuffer, sizeof(_gravity)); - sourceBuffer += sizeof(_gravity); - - memcpy(&_atmosphereCenter, sourceBuffer, sizeof(_atmosphereCenter)); - sourceBuffer += sizeof(_atmosphereCenter); - - memcpy(&_atmosphereInnerRadius, sourceBuffer, sizeof(_atmosphereInnerRadius)); - sourceBuffer += sizeof(_atmosphereInnerRadius); - - memcpy(&_atmosphereOuterRadius, sourceBuffer, sizeof(_atmosphereOuterRadius)); - sourceBuffer += sizeof(_atmosphereOuterRadius); - - memcpy(&_rayleighScattering, sourceBuffer, sizeof(_rayleighScattering)); - sourceBuffer += sizeof(_rayleighScattering); - - memcpy(&_mieScattering, sourceBuffer, sizeof(_mieScattering)); - sourceBuffer += sizeof(_mieScattering); - - memcpy(&_scatteringWavelengths, sourceBuffer, sizeof(_scatteringWavelengths)); - sourceBuffer += sizeof(_scatteringWavelengths); - - memcpy(&_sunLocation, sourceBuffer, sizeof(_sunLocation)); - sourceBuffer += sizeof(_sunLocation); - - memcpy(&_sunBrightness, sourceBuffer, sizeof(_sunBrightness)); - sourceBuffer += sizeof(_sunBrightness); - - return sourceBuffer - startPosition; -} - diff --git a/libraries/environment/src/EnvironmentData.h b/libraries/environment/src/EnvironmentData.h deleted file mode 100644 index daed752d4c..0000000000 --- a/libraries/environment/src/EnvironmentData.h +++ /dev/null @@ -1,83 +0,0 @@ -// -// EnvironmentData.h -// libraries/environment/src -// -// Created by Andrzej Kapolka on 5/6/13. -// Copyright 2013 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_EnvironmentData_h -#define hifi_EnvironmentData_h - -#include - -class EnvironmentData { -public: - - EnvironmentData(int id = 0); - - void setID(int id) { _id = id; } - int getID() const { return _id; } - - void setFlat(bool flat) { _flat = flat; } - bool isFlat() const { return _flat; } - - void setGravity(float gravity) { _gravity = gravity; } - float getGravity() const { return _gravity; } - - void setHasStars(bool value) { _hasStars = value; } - bool getHasStars() const { return _hasStars; } - - void setAtmosphereCenter(const glm::vec3& center) { _atmosphereCenter = center; } - void setAtmosphereInnerRadius(float radius) { _atmosphereInnerRadius = radius; } - void setAtmosphereOuterRadius(float radius) { _atmosphereOuterRadius = radius; } - const glm::vec3& getAtmosphereCenter() const { return _atmosphereCenter; } - float getAtmosphereInnerRadius() const { return _atmosphereInnerRadius; } - float getAtmosphereOuterRadius() const { return _atmosphereOuterRadius; } - - void setRayleighScattering(float scattering) { _rayleighScattering = scattering; } - void setMieScattering(float scattering) { _mieScattering = scattering; } - float getRayleighScattering() const { return _rayleighScattering; } - float getMieScattering() const { return _mieScattering; } - - void setScatteringWavelengths(const glm::vec3& wavelengths) { _scatteringWavelengths = wavelengths; } - const glm::vec3& getScatteringWavelengths() const { return _scatteringWavelengths; } - - void setSunLocation(const glm::vec3& location) { _sunLocation = location; } - void setSunBrightness(float brightness) { _sunBrightness = brightness; } - const glm::vec3& getSunLocation() const { return _sunLocation; } - float getSunBrightness() const { return _sunBrightness; } - - glm::vec3 getAtmosphereCenter(const glm::vec3& cameraPosition) const; - glm::vec3 getSunLocation(const glm::vec3& cameraPosition) const; - - size_t getBroadcastData(unsigned char* destinationBuffer) const; - size_t parseData(const unsigned char* sourceBuffer, int numBytes); - -private: - - int _id; - - bool _flat; - - float _gravity; - - glm::vec3 _atmosphereCenter; - float _atmosphereInnerRadius; - float _atmosphereOuterRadius; - - float _rayleighScattering; - float _mieScattering; - - glm::vec3 _scatteringWavelengths; - - glm::vec3 _sunLocation; - float _sunBrightness; - - bool _hasStars; -}; - -#endif // hifi_EnvironmentData_h diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1be3bbb5f6..70de307f1b 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1035,7 +1035,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } else if (type.contains("transparentcolor")) { // it should be TransparentColor... // THis is how Maya assign a texture that affect diffuse color AND transparency ? - diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + opacityTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("bump")) { bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("normal")) { diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 51cb5baf9f..8b1b0d2588 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -402,6 +402,7 @@ public: QHash specularTextures; QHash emissiveTextures; QHash ambientTextures; + QHash opacityTextures; QHash _fbxMaterials; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index e947a0356e..1329fccc3a 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -70,8 +70,7 @@ void FBXReader::consolidateFBXMaterials() { FBXTexture diffuseTexture; QString diffuseTextureID = diffuseTextures.value(material.materialID); if (!diffuseTextureID.isNull()) { - diffuseTexture = getTexture(diffuseTextureID); - + diffuseTexture = getTexture(diffuseTextureID); // FBX files generated by 3DSMax have an intermediate texture parent, apparently foreach (const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { if (_textureFilenames.contains(childTextureID)) { @@ -80,9 +79,16 @@ void FBXReader::consolidateFBXMaterials() { } material.diffuseTexture = diffuseTexture; - detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); } + + FBXTexture opacityTexture; + QString opacityTextureID = opacityTextures.value(material.materialID); + if (!opacityTextureID.isNull()) { + opacityTexture = getTexture(opacityTextureID); + material.opacityTexture = opacityTexture; + } + FBXTexture normalTexture; QString bumpTextureID = bumpTextures.value(material.materialID); diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 32cb1131f2..813386132c 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -628,3 +628,30 @@ QQmlContext* OffscreenQmlSurface::getRootContext() { return _qmlEngine->rootContext(); } +Q_DECLARE_METATYPE(std::function); +static auto VoidLambdaType = qRegisterMetaType>(); +Q_DECLARE_METATYPE(std::function); +static auto VariantLambdaType = qRegisterMetaType>(); + + +void OffscreenQmlSurface::executeOnUiThread(std::function function, bool blocking ) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "executeOnUiThread", blocking ? Qt::BlockingQueuedConnection : Qt::QueuedConnection, + Q_ARG(std::function, function)); + return; + } + + function(); +} + +QVariant OffscreenQmlSurface::returnFromUiThread(std::function function) { + if (QThread::currentThread() != thread()) { + QVariant result; + QMetaObject::invokeMethod(this, "returnFromUiThread", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, result), + Q_ARG(std::function, function)); + return result; + } + + return function(); +} diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 608e811b4b..fb916178ad 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -45,6 +45,9 @@ public: return load(QUrl(qmlSourceFile), f); } + Q_INVOKABLE void executeOnUiThread(std::function function, bool blocking = false); + Q_INVOKABLE QVariant returnFromUiThread(std::function function); + void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } // Optional values for event handling void setProxyWindow(QWindow* window); diff --git a/libraries/gpu/src/gpu/GLBackendQuery.cpp b/libraries/gpu/src/gpu/GLBackendQuery.cpp index 0a76d38963..f3a81d504c 100644 --- a/libraries/gpu/src/gpu/GLBackendQuery.cpp +++ b/libraries/gpu/src/gpu/GLBackendQuery.cpp @@ -98,7 +98,11 @@ void GLBackend::do_getQuery(Batch& batch, size_t paramOffset) { glGetQueryObjectui64vEXT(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); #endif #else - glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); + glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); + if (glquery->_result == GL_TRUE) { + glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); + query->triggerReturnHandler(glquery->_result); + } #endif (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index 17802ae6ed..d84d3e3967 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -146,6 +146,68 @@ public: case gpu::RGB: case gpu::RGBA: texel.internalFormat = GL_RED; + switch (dstFormat.getType()) { + case gpu::UINT32: { + texel.internalFormat = GL_R32UI; + break; + } + case gpu::INT32: { + texel.internalFormat = GL_R32I; + break; + } + case gpu::NUINT32: { + texel.internalFormat = GL_RED; + break; + } + case gpu::NINT32: { + texel.internalFormat = GL_RED_SNORM; + break; + } + case gpu::FLOAT: { + texel.internalFormat = GL_R32F; + break; + } + case gpu::UINT16: { + texel.internalFormat = GL_R16UI; + break; + } + case gpu::INT16: { + texel.internalFormat = GL_R16I; + break; + } + case gpu::NUINT16: { + texel.internalFormat = GL_R16; + break; + } + case gpu::NINT16: { + texel.internalFormat = GL_R16_SNORM; + break; + } + case gpu::HALF: { + texel.internalFormat = GL_R16F; + break; + } + case gpu::UINT8: { + texel.internalFormat = GL_R8UI; + break; + } + case gpu::INT8: { + texel.internalFormat = GL_R8I; + break; + } + case gpu::NUINT8: { + texel.internalFormat = GL_R8; + break; + } + case gpu::NINT8: { + texel.internalFormat = GL_R8_SNORM; + break; + } + case gpu::NUM_TYPES: { // quiet compiler + Q_UNREACHABLE(); + } + + } break; case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it diff --git a/libraries/gpu/src/gpu/Query.cpp b/libraries/gpu/src/gpu/Query.cpp index b8ed729c99..2e28dcd061 100644 --- a/libraries/gpu/src/gpu/Query.cpp +++ b/libraries/gpu/src/gpu/Query.cpp @@ -10,11 +10,13 @@ // #include "Query.h" -#include +#include "GPULogging.h" +#include "Batch.h" using namespace gpu; -Query::Query() +Query::Query(const Handler& returnHandler) : + _returnHandler(returnHandler) { } @@ -22,6 +24,48 @@ Query::~Query() { } -double Query::getElapsedTime() { - return 0.0; +double Query::getElapsedTime() const { + return ((double) _queryResult) * 0.000001; } + +void Query::triggerReturnHandler(uint64_t queryResult) { + _queryResult = queryResult; + if (_returnHandler) { + _returnHandler(*this); + } +} + + +RangeTimer::RangeTimer() { + for (int i = 0; i < QUERY_QUEUE_SIZE; i++) { + _timerQueries.push_back(std::make_shared([&, i] (const Query& query) { + _tailIndex ++; + auto elapsedTime = query.getElapsedTime(); + _movingAverage.addSample(elapsedTime); + })); + } +} + +void RangeTimer::begin(gpu::Batch& batch) { + _headIndex++; + batch.beginQuery(_timerQueries[rangeIndex(_headIndex)]); +} +void RangeTimer::end(gpu::Batch& batch) { + if (_headIndex < 0) { + return; + } + batch.endQuery(_timerQueries[rangeIndex(_headIndex)]); + + if (_tailIndex < 0) { + _tailIndex = _headIndex; + } + + // Pull the previous tail query hopping to see it return + if (_tailIndex != _headIndex) { + batch.getQuery(_timerQueries[rangeIndex(_tailIndex)]); + } +} + +double RangeTimer::getAverage() const { + return _movingAverage.average; +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index 25897c5c91..8e4026fe0f 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -13,26 +13,59 @@ #include #include +#include #include +#include #include "Format.h" namespace gpu { + class Batch; + class Query { public: - Query(); + using Handler = std::function; + + Query(const Handler& returnHandler); ~Query(); - uint32 queryResult; - - double getElapsedTime(); + double getElapsedTime() const; const GPUObjectPointer gpuObject {}; + void triggerReturnHandler(uint64_t queryResult); + protected: + Handler _returnHandler; + + uint64_t _queryResult = 0; }; typedef std::shared_ptr QueryPointer; typedef std::vector< QueryPointer > Queries; + + + // gpu RangeTimer is just returning an estimate of the time taken by a chunck of work delimited by the + // begin and end calls repeated for several times. + // The result is always a late average of the time spent for that same task a few cycles ago. + class RangeTimer { + public: + RangeTimer(); + void begin(gpu::Batch& batch); + void end(gpu::Batch& batch); + + double getAverage() const; + + protected: + + static const int QUERY_QUEUE_SIZE { 4 }; + + gpu::Queries _timerQueries; + int _headIndex = -1; + int _tailIndex = -1; + MovingAverage _movingAverage; + + int rangeIndex(int index) const { return (index % QUERY_QUEUE_SIZE); } + }; }; #endif diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2b425351f1..ef649fd7ae 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -155,6 +155,8 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u material->specularTexture = textureCache->getTexture(url); } else if (material->emissiveTextureName == name) { material->emissiveTexture = textureCache->getTexture(url); + } else if (material->opacityTextureName == name) { + material->opacityTexture = textureCache->getTexture(url); } } } else { @@ -286,6 +288,16 @@ static NetworkMaterial* buildNetworkMaterial(const FBXMaterial& material, const material._material->setTextureMap(model::MaterialKey::DIFFUSE_MAP, diffuseMap); } + if (!material.opacityTexture.filename.isEmpty()) { + networkMaterial->opacityTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.opacityTexture.filename)), DEFAULT_TEXTURE, material.opacityTexture.content); + networkMaterial->opacityTextureName = material.opacityTexture.name; + + auto opacityMap = model::TextureMapPointer(new model::TextureMap()); + opacityMap->setTextureSource(networkMaterial->opacityTexture->_textureSource); + opacityMap->setTextureTransform(material.opacityTexture.transform); + + material._material->setTextureMap(model::MaterialKey::TRANSPARENT_MAP, opacityMap); + } if (!material.normalTexture.filename.isEmpty()) { networkMaterial->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.normalTexture.filename)), (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE), material.normalTexture.content); networkMaterial->normalTextureName = material.normalTexture.name; diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index b2d81c5900..53f17f4e6c 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -182,6 +182,8 @@ public: QSharedPointer specularTexture; QString emissiveTextureName; QSharedPointer emissiveTexture; + QString opacityTextureName; + QSharedPointer opacityTexture; }; diff --git a/libraries/model/src/model/Atmosphere.slh b/libraries/model/src/model/Atmosphere.slh deleted file mode 100755 index b8f390de0a..0000000000 --- a/libraries/model/src/model/Atmosphere.slh +++ /dev/null @@ -1,245 +0,0 @@ - -<@if not MODEL_ATMOSPHERE_SLH@> -<@def MODEL_ATMOSPHERE_SLH@> - - - -struct Atmosphere { - vec4 _invWaveLength; - vec4 _radiuses; - vec4 _scales; - vec4 _scatterings; - vec4 _control; -}; - -const int numSamples = 2; - -vec3 getAtmosphereInvWaveLength(Atmosphere a) { return a._invWaveLength.xyz; } // 1 / pow(wavelength, 4) for the red, green, and blue channels - -float getAtmosphereInnerRadius(Atmosphere a) { return a._radiuses.x; } // The inner (planetary) radius -float getAtmosphereOuterRadius(Atmosphere a) { return a._radiuses.y; } // The outer (atmosphere) radius - -float getAtmosphereScale(Atmosphere a) { return a._scales.x; } // 1 / (outerRadius - innerRadius) -float getAtmosphereScaleDepth(Atmosphere a) { return a._scales.y; } // The scale depth (i.e. the altitude at which the atmosphere's average density is found) -float getAtmosphereScaleOverScaleDepth(Atmosphere a) { return a._scales.z; } // scale / scaleDepth - -vec4 getAtmosphereScattering(Atmosphere a) { return a._scatterings; } // The full Mie and Rayleigh scattering coefficients -float getAtmosphereKrESun(Atmosphere a) { return a._scatterings.x; } // Kr * ESun -float getAtmosphereKmESun(Atmosphere a) { return a._scatterings.y; } // Km * ESun -float getAtmosphereKr4PI(Atmosphere a) { return a._scatterings.z; } // Kr * 4 * PI -float getAtmosphereKm4PI(Atmosphere a) { return a._scatterings.w; } // Km * 4 * PI - -float getAtmosphereNumSamples(Atmosphere a) { return a._control.x; } // numSamples -vec2 getAtmosphereGAndG2(Atmosphere a) { return a._control.yz; } // g and g2 - -float atmosphereScale(float scaleDepth, float fCos) -{ - float x = 1.0 - fCos; - return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); -} - -vec4 evalAtmosphereContribution(Atmosphere atmospheric, vec3 position, vec3 cameraPos, vec3 lightPos) { - float fInnerRadius = getAtmosphereInnerRadius(atmospheric); - float fSamples = getAtmosphereNumSamples(atmospheric); - - vec3 v3InvWavelength = getAtmosphereInvWaveLength(atmospheric); - vec4 scatteringCoefs = getAtmosphereScattering(atmospheric); - float fKrESun = scatteringCoefs.x; - float fKmESun = scatteringCoefs.y; - float fKr4PI = scatteringCoefs.z; - float fKm4PI = scatteringCoefs.w; - - vec2 gAndg2 = getAtmosphereGAndG2(atmospheric); - float g = gAndg2.x; - float g2 = gAndg2.y; - - float fScale = getAtmosphereScale(atmospheric); - float fScaleDepth = getAtmosphereScaleDepth(atmospheric); - float fScaleOverScaleDepth = getAtmosphereScaleOverScaleDepth(atmospheric); - - // Get the ray from the camera to the vertex, and its length (which is the far point of the ray passing through the atmosphere) - vec3 v3Pos = position; - vec3 v3Ray = v3Pos - cameraPos; - float fFar = length(v3Ray); - v3Ray /= fFar; - - // Calculate the ray's starting position, then calculate its scattering offset - vec3 v3Start = cameraPos; - float fHeight = length(v3Start); - float fDepthStart = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight)); - float fStartAngle = dot(v3Ray, v3Start) / fHeight; - float fStartOffset = fDepthStart * atmosphereScale(fScaleDepth, fStartAngle); - - // Initialize the scattering loop variables - //gl_FrontColor = vec4(0.0, 0.0, 0.0, 0.0); - float fSampleLength = fFar / fSamples; - float fScaledLength = fSampleLength * fScale; - - vec3 v3SampleRay = v3Ray * fSampleLength; - vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; - - // Now loop through the sample rays - vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); - // int nSamples = numSamples; - int nSamples = int(fSamples); - for(int i=0; i -uniform atmosphereBuffer { - Atmosphere _atmosphere; -}; -Atmosphere getAtmosphere() { - return _atmosphere; -} -<@else@> -uniform vec4 atmosphereBuffer[9]; -Atmosphere getAtmosphere() { - Atmosphere atmosphere; - atmosphere._invWaveLength = atmosphereBuffer[0]; - atmosphere._radiuses = atmosphereBuffer[1]; - atmosphere._scales = atmosphereBuffer[2]; - atmosphere._scatterings = atmosphereBuffer[3]; - atmosphere._control = atmosphereBuffer[4]; - - return atmosphere; -} -<@endif@> - - - -<@endif@> diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index 47d3ba3063..0ff613bd4b 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -133,56 +133,6 @@ void EarthSunModel::setSunLongitude(float lon) { invalidate(); } -Atmosphere::Atmosphere() { - // only if created from nothing shall we create the Buffer to store the properties - Data data; - _dataBuffer = gpu::BufferView(std::make_shared(sizeof(Data), (const gpu::Byte*) &data)); - - setScatteringWavelength(_scatteringWavelength); - setRayleighScattering(_rayleighScattering); - setInnerOuterRadiuses(getInnerRadius(), getOuterRadius()); -} - -void Atmosphere::setScatteringWavelength(Vec3 wavelength) { - _scatteringWavelength = wavelength; - Data& data = editData(); - data._invWaveLength = Vec4(1.0f / powf(wavelength.x, 4.0f), 1.0f / powf(wavelength.y, 4.0f), 1.0f / powf(wavelength.z, 4.0f), 0.0f); -} - -void Atmosphere::setRayleighScattering(float scattering) { - _rayleighScattering = scattering; - updateScattering(); -} - -void Atmosphere::setMieScattering(float scattering) { - _mieScattering = scattering; - updateScattering(); -} - -void Atmosphere::setSunBrightness(float brightness) { - _sunBrightness = brightness; - updateScattering(); -} - -void Atmosphere::updateScattering() { - Data& data = editData(); - - data._scatterings.x = getRayleighScattering() * getSunBrightness(); - data._scatterings.y = getMieScattering() * getSunBrightness(); - - data._scatterings.z = getRayleighScattering() * 4.0f * glm::pi(); - data._scatterings.w = getMieScattering() * 4.0f * glm::pi(); -} - -void Atmosphere::setInnerOuterRadiuses(float inner, float outer) { - Data& data = editData(); - data._radiuses.x = inner; - data._radiuses.y = outer; - data._scales.x = 1.0f / (outer - inner); - data._scales.z = data._scales.x / data._scales.y; -} - - const int NUM_DAYS_PER_YEAR = 365; const float NUM_HOURS_PER_DAY = 24.0f; const float NUM_HOURS_PER_HALF_DAY = NUM_HOURS_PER_DAY * 0.5f; diff --git a/libraries/model/src/model/Stage.h b/libraries/model/src/model/Stage.h index bf586b6b55..912a7b77a8 100644 --- a/libraries/model/src/model/Stage.h +++ b/libraries/model/src/model/Stage.h @@ -107,60 +107,6 @@ protected: static Mat4d evalWorldToGeoLocationMat(double longitude, double latitude, double altitude, double scale); }; - -class Atmosphere { -public: - - Atmosphere(); - Atmosphere(const Atmosphere& atmosphere); - Atmosphere& operator= (const Atmosphere& atmosphere); - virtual ~Atmosphere() {}; - - - void setScatteringWavelength(Vec3 wavelength); - const Vec3& getScatteringWavelength() const { return _scatteringWavelength; } - - void setRayleighScattering(float scattering); - float getRayleighScattering() const { return _rayleighScattering; } - - void setMieScattering(float scattering); - float getMieScattering() const { return _mieScattering; } - - void setSunBrightness(float brightness); - float getSunBrightness() const { return _sunBrightness; } - - void setInnerOuterRadiuses(float inner, float outer); - float getInnerRadius() const { return getData()._radiuses.x; } - float getOuterRadius() const { return getData()._radiuses.y; } - - // Data to access the attribute values of the atmosphere - class Data { - public: - Vec4 _invWaveLength = Vec4(0.0f); - Vec4 _radiuses = Vec4(6000.0f, 6025.0f, 0.0f, 0.0f); - Vec4 _scales = Vec4(0.0f, 0.25f, 0.0f, 0.0f); - Vec4 _scatterings = Vec4(0.0f); - Vec4 _control = Vec4(2.0f, -0.990f, -0.990f*-0.990f, 0.f); - - Data() {} - }; - - const UniformBufferView& getDataBuffer() const { return _dataBuffer; } - -protected: - UniformBufferView _dataBuffer; - Vec3 _scatteringWavelength = Vec3(0.650f, 0.570f, 0.475f); - float _rayleighScattering = 0.0025f; - float _mieScattering = 0.0010f; - float _sunBrightness = 20.0f; - - const Data& getData() const { return _dataBuffer.get(); } - Data& editData() { return _dataBuffer.edit(); } - - void updateScattering(); -}; -typedef std::shared_ptr< Atmosphere > AtmospherePointer; - // Sun sky stage generates the rendering primitives to display a scene realistically // at the specified location and time around earth class SunSkyStage { @@ -209,7 +155,6 @@ public: const Vec3& getSunDirection() const { return getSunLight()->getDirection(); } LightPointer getSunLight() const { valid(); return _sunLight; } - AtmospherePointer getAtmosphere() const { valid(); return _atmosphere; } enum BackgroundMode { NO_BACKGROUND = 0, @@ -229,7 +174,6 @@ protected: BackgroundMode _backgroundMode = SKY_BOX; LightPointer _sunLight; - AtmospherePointer _atmosphere; mutable SkyboxPointer _skybox; float _dayTime = 12.0f; diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 470c9145df..53ebb86b83 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME networking) setup_hifi_library(Network) link_hifi_libraries(shared) +target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") + if (WIN32) # we need ws2_32.lib on windows, but it's static so we don't bubble it up target_link_libraries(${TARGET_NAME} ws2_32.lib) @@ -31,4 +33,3 @@ endif (UNIX) # append tbb includes to our list of includes to bubble target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) -include_application_version() diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index f99e498ebf..03b33eae38 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -149,8 +149,15 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // check if it is a network address first if (handleNetworkAddress(lookupUrl.host() + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger)) { + + // if we were not passed a path, use the index path + auto path = lookupUrl.path(); + if (path.isEmpty()) { + path = INDEX_PATH; + } + // we may have a path that defines a relative viewpoint - if so we should jump to that now - handlePath(lookupUrl.path(), trigger); + handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index e6163c776b..0114bcb708 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -12,11 +12,13 @@ #include "udt/PacketHeaders.h" #include "SharedUtil.h" #include "UUID.h" +#include "ServerPathUtils.h" #include -#include +#include #include "Assignment.h" +#include Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { switch (nodeType) { @@ -64,7 +66,7 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const // this is a newly created assignment, generate a random UUID _uuid = QUuid::createUuid(); } else if (_command == Assignment::RequestCommand) { - _nodeVersion = BUILD_VERSION; + _nodeVersion = BuildInfo::VERSION; } } diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index e6c2f2985a..81a58df730 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -95,16 +95,16 @@ void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) { if (hostInfo.error() != QHostInfo::NoError) { qCDebug(networking) << "Lookup failed for" << hostInfo.lookupId() << ":" << hostInfo.errorString(); emit lookupFailed(); - } - - foreach(const QHostAddress& address, hostInfo.addresses()) { - // just take the first IPv4 address - if (address.protocol() == QAbstractSocket::IPv4Protocol) { - _address = address; - qCDebug(networking) << "QHostInfo lookup result for" - << hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString(); - emit lookupCompleted(); - break; + } else { + foreach(const QHostAddress& address, hostInfo.addresses()) { + // just take the first IPv4 address + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + _address = address; + qCDebug(networking) << "QHostInfo lookup result for" + << hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString(); + emit lookupCompleted(); + break; + } } } } diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 0b194b017e..485100da0a 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -61,7 +61,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short firstCall = false; } - + qRegisterMetaType("ConnectionStep"); _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); @@ -87,7 +87,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short // check the local socket right now updateLocalSockAddr(); - + // set &PacketReceiver::handleVerifiedPacket as the verified packet callback for the udt::Socket _nodeSocket.setPacketHandler( [this](std::unique_ptr packet) { @@ -108,8 +108,16 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short // set our isPacketVerified method as the verify operator for the udt::Socket using std::placeholders::_1; _nodeSocket.setPacketFilterOperator(std::bind(&LimitedNodeList::isPacketVerified, this, _1)); - + _packetStatTimer.start(); + + if (_stunSockAddr.getAddress().isNull()) { + // we don't know the stun server socket yet, add it to unfiltered once known + connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::addSTUNHandlerToUnfiltered); + } else { + // we know the stun server socket, add it to unfiltered now + addSTUNHandlerToUnfiltered(); + } } void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { @@ -161,42 +169,42 @@ bool LimitedNodeList::isPacketVerified(const udt::Packet& packet) { bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); - + if (headerVersion != versionForPacketType(headerType)) { - + static QMultiHash sourcedVersionDebugSuppressMap; static QMultiHash versionDebugSuppressMap; - + bool hasBeenOutput = false; QString senderString; - + if (NON_SOURCED_PACKETS.contains(headerType)) { const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, headerType); - + if (!hasBeenOutput) { versionDebugSuppressMap.insert(senderSockAddr, headerType); senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort()); } } else { QUuid sourceID = NLPacket::sourceIDInHeader(packet); - + hasBeenOutput = sourcedVersionDebugSuppressMap.contains(sourceID, headerType); - + if (!hasBeenOutput) { sourcedVersionDebugSuppressMap.insert(sourceID, headerType); senderString = uuidStringWithoutCurlyBraces(sourceID.toString()); } } - + if (!hasBeenOutput) { qCDebug(networking) << "Packet version mismatch on" << headerType << "- Sender" << senderString << "sent" << qPrintable(QString::number(headerVersion)) << "but" << qPrintable(QString::number(versionForPacketType(headerType))) << "expected."; - + emit packetVersionMismatch(headerType); } - + return false; } else { return true; @@ -204,27 +212,27 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { } bool LimitedNodeList::packetSourceAndHashMatch(const udt::Packet& packet) { - + PacketType headerType = NLPacket::typeInHeader(packet); - + if (NON_SOURCED_PACKETS.contains(headerType)) { return true; } else { QUuid sourceID = NLPacket::sourceIDInHeader(packet); - + // figure out which node this is from SharedNodePointer matchingNode = nodeWithUUID(sourceID); - + if (matchingNode) { if (!NON_VERIFIED_PACKETS.contains(headerType)) { - + QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, matchingNode->getConnectionSecret()); - + // check if the md5 hash in the header matches the hash we would expect if (packetHeaderHash != expectedHash) { static QMultiMap hashDebugSuppressMap; - + if (!hashDebugSuppressMap.contains(sourceID, headerType)) { qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID; @@ -238,15 +246,15 @@ bool LimitedNodeList::packetSourceAndHashMatch(const udt::Packet& packet) { // No matter if this packet is handled or not, we update the timestamp for the last time we heard // from this sending node matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - + return true; - + } else { static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID"; static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); - qCDebug(networking) << "Packet of type" << headerType + qCDebug(networking) << "Packet of type" << headerType << "received from unknown node with UUID" << qPrintable(uuidStringWithoutCurlyBraces(sourceID)); } } @@ -264,7 +272,7 @@ void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& conn if (!NON_SOURCED_PACKETS.contains(packet.getType())) { packet.writeSourceID(getSessionUUID()); } - + if (!connectionSecret.isNull() && !NON_SOURCED_PACKETS.contains(packet.getType()) && !NON_VERIFIED_PACKETS.contains(packet.getType())) { @@ -274,14 +282,14 @@ void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& conn qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode) { Q_ASSERT(!packet.isPartOfMessage()); - + if (!destinationNode.getActiveSocket()) { return 0; } - + emit dataSent(destinationNode.getType(), packet.getDataSize()); destinationNode.recordBytesSent(packet.getDataSize()); - + return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret()); } @@ -290,21 +298,21 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiS Q_ASSERT(!packet.isPartOfMessage()); Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", "Trying to send a reliable packet unreliably."); - + collectPacketStats(packet); fillPacketHeader(packet, connectionSecret); - + return _nodeSocket.writePacket(packet, sockAddr); } qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& destinationNode) { Q_ASSERT(!packet->isPartOfMessage()); auto activeSocket = destinationNode.getActiveSocket(); - + if (activeSocket) { emit dataSent(destinationNode.getType(), packet->getDataSize()); destinationNode.recordBytesSent(packet->getDataSize()); - + return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret()); } else { qDebug() << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending"; @@ -318,10 +326,10 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiS if (packet->isReliable()) { collectPacketStats(*packet); fillPacketHeader(*packet, connectionSecret); - + auto size = packet->getDataSize(); _nodeSocket.writePacket(std::move(packet), sockAddr); - + return size; } else { return sendUnreliablePacket(*packet, sockAddr, connectionSecret); @@ -330,18 +338,18 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiS qint64 LimitedNodeList::sendPacketList(NLPacketList& packetList, const Node& destinationNode) { auto activeSocket = destinationNode.getActiveSocket(); - + if (activeSocket) { qint64 bytesSent = 0; auto connectionSecret = destinationNode.getConnectionSecret(); - + // close the last packet in the list packetList.closeCurrentPacket(); - + while (!packetList._packets.empty()) { bytesSent += sendPacket(packetList.takeFront(), *activeSocket, connectionSecret); } - + emit dataSent(destinationNode.getType(), bytesSent); return bytesSent; } else { @@ -354,21 +362,21 @@ qint64 LimitedNodeList::sendPacketList(NLPacketList& packetList, const Node& des qint64 LimitedNodeList::sendPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, const QUuid& connectionSecret) { qint64 bytesSent = 0; - + // close the last packet in the list packetList.closeCurrentPacket(); - + while (!packetList._packets.empty()) { bytesSent += sendPacket(packetList.takeFront(), sockAddr, connectionSecret); } - + return bytesSent; } qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, const HifiSockAddr& sockAddr) { // close the last packet in the list packetList->closeCurrentPacket(); - + for (std::unique_ptr& packet : packetList->_packets) { NLPacket* nlPacket = static_cast(packet.get()); collectPacketStats(*nlPacket); @@ -383,13 +391,13 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, if (activeSocket) { // close the last packet in the list packetList->closeCurrentPacket(); - + for (std::unique_ptr& packet : packetList->_packets) { NLPacket* nlPacket = static_cast(packet.get()); collectPacketStats(*nlPacket); fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret()); } - + return _nodeSocket.writePacketList(std::move(packetList), *activeSocket); } else { qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node. Not sending."; @@ -403,11 +411,11 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node. Not sending."; return 0; } - + // use the node's active socket as the destination socket if there is no overriden socket address auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket() : overridenSockAddr; - + return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret()); } @@ -423,7 +431,7 @@ int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointergetMutex()); return linkedData->parseData(*message); } - + return 0; } @@ -453,7 +461,7 @@ void LimitedNodeList::eraseAllNodes() { } } } - + foreach(const SharedNodePointer& killedNode, killedNodes) { handleNodeKill(killedNode); } @@ -461,7 +469,7 @@ void LimitedNodeList::eraseAllNodes() { void LimitedNodeList::reset() { eraseAllNodes(); - + // we need to make sure any socket connections are gone so wait on that here _nodeSocket.clearConnections(); } @@ -474,16 +482,16 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { SharedNodePointer matchingNode = it->second; readLocker.unlock(); - + { QWriteLocker writeLocker(&_nodeMutex); _nodeHash.unsafe_erase(it); } - + handleNodeKill(matchingNode); return true; } - + return false; } @@ -499,7 +507,7 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { qCDebug(networking) << "Killed" << *node; node->stopPingTimer(); emit nodeKilled(node); - + if (auto activeSocket = node->getActiveSocket()) { _nodeSocket.cleanupConnection(*activeSocket); } @@ -553,9 +561,9 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t std::unique_ptr LimitedNodeList::constructPingPacket(PingType_t pingType) { int packetSize = sizeof(PingType_t) + sizeof(quint64); - + auto pingPacket = NLPacket::create(PacketType::Ping, packetSize); - + pingPacket->writePrimitive(pingType); pingPacket->writePrimitive(usecTimestampNow()); @@ -605,14 +613,14 @@ std::unique_ptr LimitedNodeList::constructICEPingReplyPacket(ReceivedM unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr packet, const NodeSet& destinationNodeTypes) { unsigned int n = 0; - + eachNode([&](const SharedNodePointer& node){ if (node && destinationNodeTypes.contains(node->getType())) { sendUnreliablePacket(*packet, *node); ++n; } }); - + return n; } @@ -664,46 +672,48 @@ const int NUM_BYTES_STUN_HEADER = 20; void LimitedNodeList::sendSTUNRequest() { - const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; + if (!_stunSockAddr.isNull()) { + const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; - if (!_hasCompletedInitialSTUN) { - qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; + if (!_hasCompletedInitialSTUN) { + qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; - if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) { - // we're still trying to do our initial STUN we're over the fail threshold - stopInitialSTUNUpdate(false); + if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) { + // we're still trying to do our initial STUN we're over the fail threshold + stopInitialSTUNUpdate(false); + } + + ++_numInitialSTUNRequests; } - ++_numInitialSTUNRequests; + char stunRequestPacket[NUM_BYTES_STUN_HEADER]; + + int packetIndex = 0; + + const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); + + // leading zeros + message type + const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); + memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); + packetIndex += sizeof(REQUEST_MESSAGE_TYPE); + + // message length (no additional attributes are included) + uint16_t messageLength = 0; + memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); + packetIndex += sizeof(messageLength); + + memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); + packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); + + // transaction ID (random 12-byte unsigned integer) + const uint NUM_TRANSACTION_ID_BYTES = 12; + QUuid randomUUID = QUuid::createUuid(); + memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); + + flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest); + + _nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); } - - char stunRequestPacket[NUM_BYTES_STUN_HEADER]; - - int packetIndex = 0; - - const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - - // leading zeros + message type - const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); - memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); - packetIndex += sizeof(REQUEST_MESSAGE_TYPE); - - // message length (no additional attributes are included) - uint16_t messageLength = 0; - memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); - packetIndex += sizeof(messageLength); - - memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); - packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - // transaction ID (random 12-byte unsigned integer) - const uint NUM_TRANSACTION_ID_BYTES = 12; - QUuid randomUUID = QUuid::createUuid(); - memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); - - flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest); - - _nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); } void LimitedNodeList::processSTUNResponse(std::unique_ptr packet) { @@ -722,7 +732,7 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr packe // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE while (attributeStartIndex < packet->getDataSize()) { - + if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; const int NUM_BYTES_FAMILY_ALIGN = 1; @@ -751,7 +761,7 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr packe uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); QHostAddress newPublicAddress(stunAddress); - + if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); @@ -768,7 +778,7 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr packe flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); } - + // we're done reading the packet so we can return now return; } @@ -788,38 +798,50 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr packe } void LimitedNodeList::startSTUNPublicSocketUpdate() { - assert(!_initialSTUNTimer); + if (!_initialSTUNTimer ) { + // setup our initial STUN timer here so we can quickly find out our public IP address + _initialSTUNTimer = new QTimer { this }; - if (!_initialSTUNTimer) { - // if we don't know the STUN IP yet we need to have ourselves be called once it is known + connect(_initialSTUNTimer.data(), &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); + + const int STUN_INITIAL_UPDATE_INTERVAL_MSECS = 250; + _initialSTUNTimer->setInterval(STUN_INITIAL_UPDATE_INTERVAL_MSECS); + + // if we don't know the STUN IP yet we need to wait until it is known to start STUN requests if (_stunSockAddr.getAddress().isNull()) { - connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::startSTUNPublicSocketUpdate); - connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::addSTUNHandlerToUnfiltered); - // in case we just completely fail to lookup the stun socket - add a 10s timeout that will trigger the fail case + // if we fail to lookup the socket then timeout the STUN address lookup + connect(&_stunSockAddr, &HifiSockAddr::lookupFailed, this, &LimitedNodeList::possiblyTimeoutSTUNAddressLookup); + + // immediately send a STUN request once we know the socket + connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::sendSTUNRequest); + + // start the initial STUN timer once we know the socket + connect(&_stunSockAddr, SIGNAL(lookupCompleted()), _initialSTUNTimer, SLOT(start())); + + // in case we just completely fail to lookup the stun socket - add a 10s single shot that will trigger the fail case const quint64 STUN_DNS_LOOKUP_TIMEOUT_MSECS = 10 * 1000; - QTimer* stunLookupFailTimer = new QTimer(this); - connect(stunLookupFailTimer, &QTimer::timeout, this, &LimitedNodeList::possiblyTimeoutSTUNAddressLookup); - stunLookupFailTimer->start(STUN_DNS_LOOKUP_TIMEOUT_MSECS); + QTimer* lookupTimeoutTimer = new QTimer { this }; + lookupTimeoutTimer->setSingleShot(true); + connect(lookupTimeoutTimer, &QTimer::timeout, this, &LimitedNodeList::possiblyTimeoutSTUNAddressLookup); + + // delete the lookup timeout timer once it has fired + connect(lookupTimeoutTimer, &QTimer::timeout, lookupTimeoutTimer, &QTimer::deleteLater); + + lookupTimeoutTimer->start(STUN_DNS_LOOKUP_TIMEOUT_MSECS); } else { - // setup our initial STUN timer here so we can quickly find out our public IP address - _initialSTUNTimer = new QTimer(this); + _initialSTUNTimer->start(); - connect(_initialSTUNTimer.data(), &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); - - const int STUN_INITIAL_UPDATE_INTERVAL_MSECS = 250; - _initialSTUNTimer->start(STUN_INITIAL_UPDATE_INTERVAL_MSECS); - - // send an initial STUN request right away - sendSTUNRequest(); + // send an initial STUN request right away + sendSTUNRequest(); } } } void LimitedNodeList::possiblyTimeoutSTUNAddressLookup() { - if (_stunSockAddr.getAddress().isNull()) { + if (_stunSockAddr.isNull()) { // our stun address is still NULL, but we've been waiting for long enough - time to force a fail stopInitialSTUNUpdate(false); } @@ -859,7 +881,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) { // Or, if we failed - if will check if we can eventually get a public socket const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; - QTimer* stunOccasionalTimer = new QTimer(this); + QTimer* stunOccasionalTimer = new QTimer { this }; connect(stunOccasionalTimer, &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); stunOccasionalTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); @@ -904,7 +926,7 @@ void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSoc QDataStream iceDataStream(icePacket.get()); iceDataStream << clientID << _publicSockAddr << _localSockAddr; - + if (packetType == PacketType::ICEServerQuery) { assert(!peerID.isNull()); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 87ed12ac66..c9785d240b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -293,6 +293,7 @@ protected: bool _thisNodeCanRez; QPointer _initialSTUNTimer; + int _numInitialSTUNRequests = 0; bool _hasCompletedInitialSTUN = false; quint64 _firstSTUNTime = 0; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5af2bbe7c7..b0cd8cce39 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -61,7 +60,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // in case we don't know how to talk to DS when a path change is requested // fire off any pending DS path query when we get socket information - connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendPendingDSPathQuery); + connect(&_domainHandler, &DomainHandler::connectedToDomain, this, &NodeList::sendPendingDSPathQuery); // send a domain server check in immediately once the DS socket is known connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendDomainServerCheckIn); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 46bf41398c..4e6418279d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -41,7 +41,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITITES_HAVE_COLLISION_MASK; + return VERSION_ATMOSPHERE_REMOVED; case PacketType::AvatarData: case PacketType::BulkAvatarData: return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b8637e85cb..af3fd49710 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -165,6 +165,7 @@ const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_ const PacketVersion VERSION_MODEL_ENTITIES_JOINTS_ON_WIRE = 53; const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54; const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55; +const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index b579d4442d..152ae7be23 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -67,7 +67,7 @@ public: virtual glm::vec3 getObjectGravity() const override { return _entity->getGravity(); } virtual glm::vec3 getObjectLinearVelocityChange() const override; - virtual const QUuid& getObjectID() const override { return _entity->getID(); } + virtual const QUuid getObjectID() const override { return _entity->getID(); } virtual quint8 getSimulationPriority() const override; virtual QUuid getSimulatorID() const override; diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index efab75b802..4e3390b386 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -40,8 +40,8 @@ public: virtual void updateActionWorker(float deltaTimeStep) = 0; // these are from btActionInterface - virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); - virtual void debugDraw(btIDebugDraw* debugDrawer); + virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) override; + virtual void debugDraw(btIDebugDraw* debugDrawer) override; virtual QByteArray serialize() const override = 0; virtual void deserialize(QByteArray serializedArguments) override = 0; diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 539327ce0c..073571c1c9 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -128,7 +128,7 @@ public: virtual glm::vec3 getObjectAngularVelocity() const = 0; virtual glm::vec3 getObjectGravity() const = 0; - virtual const QUuid& getObjectID() const = 0; + virtual const QUuid getObjectID() const = 0; virtual quint8 getSimulationPriority() const { return 0; } virtual QUuid getSimulatorID() const = 0; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 9ef27aaf53..a677cfae4d 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -142,6 +142,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // finally clear all lists maintained by this class _physicalObjects.clear(); _entitiesToRemoveFromPhysics.clear(); + _entitiesToRelease.clear(); _entitiesToAddToPhysics.clear(); _pendingChanges.clear(); _outgoingChanges.clear(); @@ -157,6 +158,7 @@ void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) // end EntitySimulation overrides void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { + _entitiesToRelease.clear(); result.clear(); QMutexLocker lock(&_mutex); for (auto entity: _entitiesToRemoveFromPhysics) { @@ -171,7 +173,7 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState _entitiesToDelete.insert(entity); } } - _entitiesToRemoveFromPhysics.clear(); + _entitiesToRemoveFromPhysics.swap(_entitiesToRelease); } void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index bc80d50d0a..1c783d2f31 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -60,6 +60,7 @@ public: private: SetOfEntities _entitiesToRemoveFromPhysics; + SetOfEntities _entitiesToRelease; SetOfEntities _entitiesToAddToPhysics; SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed @@ -70,7 +71,7 @@ private: PhysicsEnginePointer _physicsEngine = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr; - uint32_t _lastStepSendPackets = 0; + uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index c74089a238..c4f28aeffd 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render) # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Widgets OpenGL Network Qml Quick Script) -link_hifi_libraries(shared gpu procedural model model-networking render environment animation fbx) +link_hifi_libraries(shared gpu procedural model model-networking render animation fbx) target_nsight() target_oglplus() diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 1a4cfde16f..815cb45423 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -24,7 +24,6 @@ class Transform; class QThread; class ViewFrustum; class PickRay; -class EnvironmentData; /// Interface provided by Application to other objects that need access to the current view state details class AbstractViewStateInterface { @@ -32,10 +31,6 @@ public: /// gets the current view frustum for rendering the view state virtual ViewFrustum* getCurrentViewFrustum() = 0; - /// overrides environment data - virtual void overrideEnvironmentData(const EnvironmentData& newData) = 0; - virtual void endOverrideEnvironmentData() = 0; - /// gets the shadow view frustum for rendering the view state virtual ViewFrustum* getShadowViewFrustum() = 0; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 0dd2e080ec..30360fdbce 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -12,69 +12,135 @@ #include +#include //min max and more + + #include #include #include +#include +#include "RenderUtilsLogging.h" #include "AmbientOcclusionEffect.h" #include "TextureCache.h" #include "FramebufferCache.h" #include "DependencyManager.h" #include "ViewFrustum.h" -#include "GeometryCache.h" -#include "ambient_occlusion_vert.h" -#include "ambient_occlusion_frag.h" -#include "gaussian_blur_vertical_vert.h" -#include "gaussian_blur_horizontal_vert.h" -#include "gaussian_blur_frag.h" -#include "occlusion_blend_frag.h" +#include "ssao_makePyramid_frag.h" +#include "ssao_makeOcclusion_frag.h" +#include "ssao_makeHorizontalBlur_frag.h" +#include "ssao_makeVerticalBlur_frag.h" +class GaussianDistribution { +public: + + static double integral(float x, float deviation) { + return 0.5 * erf((double)x / ((double)deviation * sqrt(2.0))); + } + + static double rangeIntegral(float x0, float x1, float deviation) { + return integral(x1, deviation) - integral(x0, deviation); + } + + static std::vector evalSampling(int samplingRadius, float deviation) { + std::vector coefs(samplingRadius + 1, 0.0f); + + // corner case when radius is 0 or under + if (samplingRadius <= 0) { + coefs[0] = 1.0f; + return coefs; + } + + // Evaluate all the samples range integral of width 1 from center until the penultimate one + float halfWidth = 0.5f; + double sum = 0.0; + for (int i = 0; i < samplingRadius; i++) { + float x = (float) i; + double sample = rangeIntegral(x - halfWidth, x + halfWidth, deviation); + coefs[i] = sample; + sum += sample; + } + + // last sample goes to infinity + float lastSampleX0 = (float) samplingRadius - halfWidth; + float largeEnough = lastSampleX0 + 1000.0f * deviation; + double sample = rangeIntegral(lastSampleX0, largeEnough, deviation); + coefs[samplingRadius] = sample; + sum += sample; + + return coefs; + } + + static void evalSampling(float* coefs, unsigned int coefsLength, int samplingRadius, float deviation) { + auto coefsVector = evalSampling(samplingRadius, deviation); + if (coefsLength> coefsVector.size() + 1) { + unsigned int coefsNum = 0; + for (auto s : coefsVector) { + coefs[coefsNum] = s; + coefsNum++; + } + for (;coefsNum < coefsLength; coefsNum++) { + coefs[coefsNum] = 0.0f; + } + } + } +}; -AmbientOcclusion::AmbientOcclusion() { +const int AmbientOcclusionEffect_FrameTransformSlot = 0; +const int AmbientOcclusionEffect_ParamsSlot = 1; +const int AmbientOcclusionEffect_DepthMapSlot = 0; +const int AmbientOcclusionEffect_PyramidMapSlot = 0; +const int AmbientOcclusionEffect_OcclusionMapSlot = 0; + +AmbientOcclusionEffect::AmbientOcclusionEffect() { + FrameTransform frameTransform; + _frameTransformBuffer = gpu::BufferView(std::make_shared(sizeof(FrameTransform), (const gpu::Byte*) &frameTransform)); + Parameters parameters; + _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); } -const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() { - if (!_occlusionPipeline) { - auto vs = gpu::Shader::createVertex(std::string(ambient_occlusion_vert)); - auto ps = gpu::Shader::createPixel(std::string(ambient_occlusion_frag)); +const gpu::PipelinePointer& AmbientOcclusionEffect::getPyramidPipeline() { + if (!_pyramidPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(ssao_makePyramid_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("depthTexture"), 0)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalTexture"), 1)); - + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AmbientOcclusionEffect_DepthMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); - _gScaleLoc = program->getUniforms().findLocation("g_scale"); - _gBiasLoc = program->getUniforms().findLocation("g_bias"); - _gSampleRadiusLoc = program->getUniforms().findLocation("g_sample_rad"); - _gIntensityLoc = program->getUniforms().findLocation("g_intensity"); - - _nearLoc = program->getUniforms().findLocation("near"); - _depthScaleLoc = program->getUniforms().findLocation("depthScale"); - _depthTexCoordOffsetLoc = program->getUniforms().findLocation("depthTexCoordOffset"); - _depthTexCoordScaleLoc = program->getUniforms().findLocation("depthTexCoordScale"); - _renderTargetResLoc = program->getUniforms().findLocation("renderTargetRes"); - _renderTargetResInvLoc = program->getUniforms().findLocation("renderTargetResInv"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(false, false, gpu::LESS_EQUAL); + // Stencil test the pyramid passe for objects pixels only, not the background + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - // Blend on transparent - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO); + state->setColorWriteMask(true, false, false, false); - // Link the occlusion FBO to texture - _occlusionBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, - DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); - auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - auto width = _occlusionBuffer->getWidth(); - auto height = _occlusionBuffer->getHeight(); - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); + // Good to go add the brand new pipeline + _pyramidPipeline = gpu::Pipeline::create(program, state); + } + return _pyramidPipeline; +} + +const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() { + if (!_occlusionPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(ssao_makeOcclusion_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_PyramidMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + state->setColorWriteMask(true, true, true, false); // Good to go add the brand new pipeline _occlusionPipeline = gpu::Pipeline::create(program, state); @@ -82,32 +148,46 @@ const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() { return _occlusionPipeline; } -const gpu::PipelinePointer& AmbientOcclusion::getVBlurPipeline() { - if (!_vBlurPipeline) { - auto vs = gpu::Shader::createVertex(std::string(gaussian_blur_vertical_vert)); - auto ps = gpu::Shader::createPixel(std::string(gaussian_blur_frag)); + +const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() { + if (!_hBlurPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(ssao_makeHorizontalBlur_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); - + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(false, false, gpu::LESS_EQUAL); + state->setColorWriteMask(true, true, true, false); + + // Good to go add the brand new pipeline + _hBlurPipeline = gpu::Pipeline::create(program, state); + } + return _hBlurPipeline; +} - // Blend on transparent - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO); - - // Link the horizontal blur FBO to texture - _vBlurBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, - DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); - auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - auto width = _vBlurBuffer->getWidth(); - auto height = _vBlurBuffer->getHeight(); - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _vBlurTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); +const gpu::PipelinePointer& AmbientOcclusionEffect::getVBlurPipeline() { + if (!_vBlurPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(ssao_makeVerticalBlur_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot)); + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Vertical blur write just the final result Occlusion value in the alpha channel + state->setColorWriteMask(true, true, true, false); // Good to go add the brand new pipeline _vBlurPipeline = gpu::Pipeline::create(program, state); @@ -115,171 +195,228 @@ const gpu::PipelinePointer& AmbientOcclusion::getVBlurPipeline() { return _vBlurPipeline; } -const gpu::PipelinePointer& AmbientOcclusion::getHBlurPipeline() { - if (!_hBlurPipeline) { - auto vs = gpu::Shader::createVertex(std::string(gaussian_blur_horizontal_vert)); - auto ps = gpu::Shader::createPixel(std::string(gaussian_blur_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - - state->setDepthTest(false, false, gpu::LESS_EQUAL); - - // Blend on transparent - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO); - - // Link the horizontal blur FBO to texture - _hBlurBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, - DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height())); - auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - auto width = _hBlurBuffer->getWidth(); - auto height = _hBlurBuffer->getHeight(); - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _hBlurTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); - - // Good to go add the brand new pipeline - _hBlurPipeline = gpu::Pipeline::create(program, state); - } - return _hBlurPipeline; +void AmbientOcclusionEffect::setDepthInfo(float nearZ, float farZ) { + _frameTransformBuffer.edit().depthInfo = glm::vec4(nearZ*farZ, farZ -nearZ, -farZ, 0.0f); } -const gpu::PipelinePointer& AmbientOcclusion::getBlendPipeline() { - if (!_blendPipeline) { - auto vs = gpu::Shader::createVertex(std::string(ambient_occlusion_vert)); - auto ps = gpu::Shader::createPixel(std::string(occlusion_blend_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); +void AmbientOcclusionEffect::setResolutionLevel(int level) { + const int MAX_RESOLUTION_LEVEL = 4; + level = std::max(0, std::min(level, MAX_RESOLUTION_LEVEL)); + if (level != getResolutionLevel()) { + auto& current = _parametersBuffer.edit().resolutionInfo; + current.x = (float)level; - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("blurredOcclusionTexture"), 0)); - - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - - state->setDepthTest(false, false, gpu::LESS_EQUAL); - - // Blend on transparent - state->setBlendFunction(true, - gpu::State::INV_SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::SRC_ALPHA); - - // Good to go add the brand new pipeline - _blendPipeline = gpu::Pipeline::create(program, state); + // Communicate the change to the Framebuffer cache + DependencyManager::get()->setAmbientOcclusionResolutionLevel(level); } - return _blendPipeline; } -void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { +void AmbientOcclusionEffect::setRadius(float radius) { + const double RADIUS_POWER = 6.0; + radius = std::max(0.01f, radius); + if (radius != getRadius()) { + auto& current = _parametersBuffer.edit().radiusInfo; + current.x = radius; + current.y = radius * radius; + current.z = (float)(1.0 / pow((double)radius, RADIUS_POWER)); + } +} + +void AmbientOcclusionEffect::setLevel(float level) { + level = std::max(0.01f, level); + if (level != getLevel()) { + auto& current = _parametersBuffer.edit().radiusInfo; + current.w = level; + } +} + +void AmbientOcclusionEffect::setDithering(bool enabled) { + if (enabled != isDitheringEnabled()) { + auto& current = _parametersBuffer.edit().ditheringInfo; + current.x = (float)enabled; + } +} + +void AmbientOcclusionEffect::setBordering(bool enabled) { + if (enabled != isBorderingEnabled()) { + auto& current = _parametersBuffer.edit().ditheringInfo; + current.w = (float)enabled; + } +} + +void AmbientOcclusionEffect::setFalloffBias(float bias) { + bias = std::max(0.0f, std::min(bias, 0.2f)); + if (bias != getFalloffBias()) { + auto& current = _parametersBuffer.edit().ditheringInfo; + current.z = (float)bias; + } +} + + +void AmbientOcclusionEffect::setNumSamples(int numSamples) { + numSamples = std::max(1.0f, (float) numSamples); + if (numSamples != getNumSamples()) { + auto& current = _parametersBuffer.edit().sampleInfo; + current.x = numSamples; + current.y = 1.0f / numSamples; + } +} + +void AmbientOcclusionEffect::setNumSpiralTurns(float numTurns) { + numTurns = std::max(0.0f, (float)numTurns); + if (numTurns != getNumSpiralTurns()) { + auto& current = _parametersBuffer.edit().sampleInfo; + current.z = numTurns; + } +} + +void AmbientOcclusionEffect::setEdgeSharpness(float sharpness) { + sharpness = std::max(0.0f, (float)sharpness); + if (sharpness != getEdgeSharpness()) { + auto& current = _parametersBuffer.edit().blurInfo; + current.x = sharpness; + } +} + +void AmbientOcclusionEffect::setBlurRadius(int radius) { + const int MAX_BLUR_RADIUS = 6; + radius = std::max(0, std::min(MAX_BLUR_RADIUS, radius)); + if (radius != getBlurRadius()) { + auto& current = _parametersBuffer.edit().blurInfo; + current.y = (float)radius; + updateGaussianDistribution(); + } +} + +void AmbientOcclusionEffect::setBlurDeviation(float deviation) { + deviation = std::max(0.0f, deviation); + if (deviation != getBlurDeviation()) { + auto& current = _parametersBuffer.edit().blurInfo; + current.z = deviation; + updateGaussianDistribution(); + } +} +void AmbientOcclusionEffect::updateGaussianDistribution() { + auto coefs = _parametersBuffer.edit()._gaussianCoefs; + GaussianDistribution::evalSampling(coefs, Parameters::GAUSSIAN_COEFS_LENGTH, getBlurRadius(), getBlurDeviation()); +} + +void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->getArgs()); assert(renderContext->getArgs()->_viewFrustum); RenderArgs* args = renderContext->getArgs(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - auto framebufferCache = DependencyManager::get(); - QSize framebufferSize = framebufferCache->getFrameBufferSize(); - float fbWidth = framebufferSize.width(); - float fbHeight = framebufferSize.height(); - float sMin = args->_viewport.x / fbWidth; - float sWidth = args->_viewport.z / fbWidth; - float tMin = args->_viewport.y / fbHeight; - float tHeight = args->_viewport.w / fbHeight; + + auto framebufferCache = DependencyManager::get(); + auto depthBuffer = framebufferCache->getPrimaryDepthTexture(); + auto normalBuffer = framebufferCache->getDeferredNormalTexture(); + auto pyramidFBO = framebufferCache->getDepthPyramidFramebuffer(); + auto occlusionFBO = framebufferCache->getOcclusionFramebuffer(); + auto occlusionBlurredFBO = framebufferCache->getOcclusionBlurredFramebuffer(); + + QSize framebufferSize = framebufferCache->getFrameBufferSize(); + float sMin = args->_viewport.x / (float)framebufferSize.width(); + float sWidth = args->_viewport.z / (float)framebufferSize.width(); + float tMin = args->_viewport.y / (float)framebufferSize.height(); + float tHeight = args->_viewport.w / (float)framebufferSize.height(); + + auto resolutionLevel = getResolutionLevel(); + + // Update the depth info with near and far (same for stereo) + setDepthInfo(args->_viewFrustum->getNearClip(), args->_viewFrustum->getFarClip()); + + _frameTransformBuffer.edit().pixelInfo = args->_viewport; + //_parametersBuffer.edit()._ditheringInfo.y += 0.25f; + + // Running in stero ? + bool isStereo = args->_context->isStereo(); + if (!isStereo) { + // Eval the mono projection + mat4 monoProjMat; + args->_viewFrustum->evalProjectionMatrix(monoProjMat); + _frameTransformBuffer.edit().projection[0] = monoProjMat; + _frameTransformBuffer.edit().stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); + + } else { + + mat4 projMats[2]; + mat4 eyeViews[2]; + args->_context->getStereoProjections(projMats); + args->_context->getStereoViews(eyeViews); + + for (int i = 0; i < 2; i++) { + // Compose the mono Eye space to Stereo clip space Projection Matrix + auto sideViewMat = projMats[i] * eyeViews[i]; + _frameTransformBuffer.edit().projection[i] = sideViewMat; + } + + _frameTransformBuffer.edit().stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); + + } + + auto pyramidPipeline = getPyramidPipeline(); + auto occlusionPipeline = getOcclusionPipeline(); + auto firstHBlurPipeline = getHBlurPipeline(); + auto lastVBlurPipeline = getVBlurPipeline(); + + gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + batch.enableStereo(false); + + _gpuTimer.begin(batch); + + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(glm::mat4()); + batch.setViewTransform(Transform()); + + Transform model; + model.setTranslation(glm::vec3(sMin, tMin, 0.0f)); + model.setScale(glm::vec3(sWidth, tHeight, 1.0f)); + batch.setModelTransform(model); + + batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, _frameTransformBuffer); + batch.setUniformBuffer(AmbientOcclusionEffect_ParamsSlot, _parametersBuffer); - glm::mat4 projMat; - Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - batch.setModelTransform(Transform()); + // Pyramid pass + batch.setFramebuffer(pyramidFBO); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(args->_viewFrustum->getFarClip(), 0.0f, 0.0f, 0.0f)); + batch.setPipeline(pyramidPipeline); + batch.setResourceTexture(AmbientOcclusionEffect_DepthMapSlot, depthBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); - // Occlusion step - getOcclusionPipeline(); - batch.setResourceTexture(0, framebufferCache->getPrimaryDepthTexture()); - batch.setResourceTexture(1, framebufferCache->getDeferredNormalTexture()); - _occlusionBuffer->setRenderBuffer(0, _occlusionTexture); - batch.setFramebuffer(_occlusionBuffer); + // Make pyramid mips + batch.generateTextureMips(pyramidFBO->getRenderBuffer(0)); - // Occlusion uniforms - g_scale = 1.0f; - g_bias = 1.0f; - g_sample_rad = 1.0f; - g_intensity = 1.0f; + // Adjust Viewport for rendering resolution + if (resolutionLevel > 0) { + glm::ivec4 viewport(args->_viewport.x, args->_viewport.y, args->_viewport.z >> resolutionLevel, args->_viewport.w >> resolutionLevel); + batch.setViewportTransform(viewport); + } - // Bind the first gpu::Pipeline we need - for calculating occlusion buffer - batch.setPipeline(getOcclusionPipeline()); - batch._glUniform1f(_gScaleLoc, g_scale); - batch._glUniform1f(_gBiasLoc, g_bias); - batch._glUniform1f(_gSampleRadiusLoc, g_sample_rad); - batch._glUniform1f(_gIntensityLoc, g_intensity); + // Occlusion pass + batch.setFramebuffer(occlusionFBO); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(1.0f)); + batch.setPipeline(occlusionPipeline); + batch.setResourceTexture(AmbientOcclusionEffect_PyramidMapSlot, pyramidFBO->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); - // setup uniforms for unpacking a view-space position from the depth buffer - // This is code taken from DeferredLightEffect.render() method in DeferredLightingEffect.cpp. - // DeferredBuffer.slh shows how the unpacking is done and what variables are needed. + + if (getBlurRadius() > 0) { + // Blur 1st pass + batch.setFramebuffer(occlusionBlurredFBO); + batch.setPipeline(firstHBlurPipeline); + batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, occlusionFBO->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); - // initialize the view-space unpacking uniforms using frustum data - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; + // Blur 2nd pass + batch.setFramebuffer(occlusionFBO); + batch.setPipeline(lastVBlurPipeline); + batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, occlusionBlurredFBO->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + + _gpuTimer.end(batch); - args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - - float depthScale = (farVal - nearVal) / farVal; - float nearScale = -1.0f / nearVal; - float depthTexCoordScaleS = (right - left) * nearScale / sWidth; - float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; - float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; - float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; - - // now set the position-unpacking unforms - batch._glUniform1f(_nearLoc, nearVal); - batch._glUniform1f(_depthScaleLoc, depthScale); - batch._glUniform2f(_depthTexCoordOffsetLoc, depthTexCoordOffsetS, depthTexCoordOffsetT); - batch._glUniform2f(_depthTexCoordScaleLoc, depthTexCoordScaleS, depthTexCoordScaleT); - - batch._glUniform2f(_renderTargetResLoc, fbWidth, fbHeight); - batch._glUniform2f(_renderTargetResInvLoc, 1.0f / fbWidth, 1.0f / fbHeight); - - glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); - glm::vec2 bottomLeft(-1.0f, -1.0f); - glm::vec2 topRight(1.0f, 1.0f); - glm::vec2 texCoordTopLeft(0.0f, 0.0f); - glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - - // Vertical blur step - getVBlurPipeline(); - batch.setResourceTexture(0, _occlusionTexture); - _vBlurBuffer->setRenderBuffer(0, _vBlurTexture); - batch.setFramebuffer(_vBlurBuffer); - - // Bind the second gpu::Pipeline we need - for calculating blur buffer - batch.setPipeline(getVBlurPipeline()); - - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - - // Horizontal blur step - getHBlurPipeline(); - batch.setResourceTexture(0, _vBlurTexture); - _hBlurBuffer->setRenderBuffer(0, _hBlurTexture); - batch.setFramebuffer(_hBlurBuffer); - - // Bind the third gpu::Pipeline we need - for calculating blur buffer - batch.setPipeline(getHBlurPipeline()); - - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - - // Blend step - getBlendPipeline(); - batch.setResourceTexture(0, _hBlurTexture); - batch.setFramebuffer(framebufferCache->getDeferredFramebuffer()); - - // Bind the fourth gpu::Pipeline we need - for blending the primary color buffer with blurred occlusion texture - batch.setPipeline(getBlendPipeline()); - - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); }); } diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 55970a2d6e..dfbc5b996e 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -16,53 +16,118 @@ #include "render/DrawTask.h" -class AmbientOcclusion { + +class AmbientOcclusionEffect { public: - AmbientOcclusion(); + AmbientOcclusionEffect(); void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); - using JobModel = render::Task::Job::Model; + + void setResolutionLevel(int level); + int getResolutionLevel() const { return _parametersBuffer.get().resolutionInfo.x; } - const gpu::PipelinePointer& getOcclusionPipeline(); - const gpu::PipelinePointer& getHBlurPipeline(); - const gpu::PipelinePointer& getVBlurPipeline(); - const gpu::PipelinePointer& getBlendPipeline(); + void setRadius(float radius); + float getRadius() const { return _parametersBuffer.get().radiusInfo.x; } + + // Obscurance level which intensify or dim down the obscurance effect + void setLevel(float level); + float getLevel() const { return _parametersBuffer.get().radiusInfo.w; } + + // On to randomize the distribution of rays per pixel, should always be true + void setDithering(bool enabled); + bool isDitheringEnabled() const { return _parametersBuffer.get().ditheringInfo.x; } + + // On to avoid evaluating information from non existing pixels Out of the frame, should always be true + void setBordering(bool enabled); + bool isBorderingEnabled() const { return _parametersBuffer.get().ditheringInfo.w; } + + // Faloff Bias + void setFalloffBias(float bias); + int getFalloffBias() const { return (int)_parametersBuffer.get().ditheringInfo.z; } + + // Number of samples per pixel to evaluate the Obscurance + void setNumSamples(int numSamples); + int getNumSamples() const { return (int)_parametersBuffer.get().sampleInfo.x; } + + // Number of spiral turns defining an angle span to distribute the samples ray directions + void setNumSpiralTurns(float numTurns); + float getNumSpiralTurns() const { return _parametersBuffer.get().sampleInfo.z; } + + // Edge blurring setting + void setEdgeSharpness(float sharpness); + int getEdgeSharpness() const { return (int)_parametersBuffer.get().blurInfo.x; } + + // Blurring Radius + // 0 means no blurring + const int MAX_BLUR_RADIUS = 6; + void setBlurRadius(int radius); + int getBlurRadius() const { return (int)_parametersBuffer.get().blurInfo.y; } + + void setBlurDeviation(float deviation); + float getBlurDeviation() const { return _parametersBuffer.get().blurInfo.z; } + + + double getGPUTime() const { return _gpuTimer.getAverage(); } + + using JobModel = render::Task::Job::Model; private: - // Uniforms for AO - gpu::int32 _gScaleLoc; - gpu::int32 _gBiasLoc; - gpu::int32 _gSampleRadiusLoc; - gpu::int32 _gIntensityLoc; + void updateGaussianDistribution(); + void setDepthInfo(float nearZ, float farZ); + + typedef gpu::BufferView UniformBufferView; - gpu::int32 _nearLoc; - gpu::int32 _depthScaleLoc; - gpu::int32 _depthTexCoordOffsetLoc; - gpu::int32 _depthTexCoordScaleLoc; - gpu::int32 _renderTargetResLoc; - gpu::int32 _renderTargetResInvLoc; + // Class describing the uniform buffer with the transform info common to the AO shaders + // It s changing every frame + class FrameTransform { + public: + // Pixel info is { viemport width height and stereo on off} + glm::vec4 pixelInfo; + // Depth info is { n.f, f - n, -f} + glm::vec4 depthInfo; + // Stereo info + glm::vec4 stereoInfo { 0.0 }; + // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 projection[2]; + + FrameTransform() {} + }; + gpu::BufferView _frameTransformBuffer; + + // Class describing the uniform buffer with all the parameters common to the AO shaders + class Parameters { + public: + // Resolution info + glm::vec4 resolutionInfo { -1.0f, 0.0f, 0.0f, 0.0f }; + // radius info is { R, R^2, 1 / R^6, ObscuranceScale} + glm::vec4 radiusInfo{ 0.5f, 0.5f * 0.5f, 1.0f / (0.25f * 0.25f * 0.25f), 1.0f }; + // Dithering info + glm::vec4 ditheringInfo { 0.0f, 0.0f, 0.01f, 1.0f }; + // Sampling info + glm::vec4 sampleInfo { 11.0f, 1.0/11.0f, 7.0f, 1.0f }; + // Blurring info + glm::vec4 blurInfo { 1.0f, 3.0f, 2.0f, 0.0f }; + // gaussian distribution coefficients first is the sampling radius (max is 6) + const static int GAUSSIAN_COEFS_LENGTH = 8; + float _gaussianCoefs[GAUSSIAN_COEFS_LENGTH]; + + Parameters() {} + }; + gpu::BufferView _parametersBuffer; + const gpu::PipelinePointer& getPyramidPipeline(); + const gpu::PipelinePointer& getOcclusionPipeline(); + const gpu::PipelinePointer& getHBlurPipeline(); // first + const gpu::PipelinePointer& getVBlurPipeline(); // second - float g_scale; - float g_bias; - float g_sample_rad; - float g_intensity; - + gpu::PipelinePointer _pyramidPipeline; gpu::PipelinePointer _occlusionPipeline; gpu::PipelinePointer _hBlurPipeline; gpu::PipelinePointer _vBlurPipeline; - gpu::PipelinePointer _blendPipeline; - - gpu::FramebufferPointer _occlusionBuffer; - gpu::FramebufferPointer _hBlurBuffer; - gpu::FramebufferPointer _vBlurBuffer; - - gpu::TexturePointer _occlusionTexture; - gpu::TexturePointer _hBlurTexture; - gpu::TexturePointer _vBlurTexture; + gpu::RangeTimer _gpuTimer; }; #endif // hifi_AmbientOcclusionEffect_h diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index e1ee308883..bb45d3945a 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -33,7 +33,10 @@ enum Slots { Specular, Depth, Lighting, - Shadow + Shadow, + Pyramid, + AmbientOcclusion, + AmbientOcclusionBlurred }; static const std::string DEFAULT_DIFFUSE_SHADER { @@ -41,11 +44,7 @@ static const std::string DEFAULT_DIFFUSE_SHADER { " return vec4(pow(texture(diffuseMap, uv).xyz, vec3(1.0 / 2.2)), 1.0);" " }" }; -static const std::string DEFAULT_ALPHA_SHADER { - "vec4 getFragmentColor() {" - " return vec4(vec3(texture(diffuseMap, uv).a), 1.0);" - " }" -}; + static const std::string DEFAULT_SPECULAR_SHADER { "vec4 getFragmentColor() {" " return vec4(texture(specularMap, uv).xyz, 1.0);" @@ -58,7 +57,7 @@ static const std::string DEFAULT_ROUGHNESS_SHADER { }; static const std::string DEFAULT_NORMAL_SHADER { "vec4 getFragmentColor() {" - " return vec4(normalize(texture(normalMap, uv).xyz), 1.0);" + " return vec4(normalize(texture(normalMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);" " }" }; static const std::string DEFAULT_DEPTH_SHADER { @@ -83,6 +82,27 @@ static const std::string DEFAULT_SHADOW_SHADER { " return vec4(vec3(0.0), 1.0);" " }" }; + +static const std::string DEFAULT_PYRAMID_DEPTH_SHADER { + "vec4 getFragmentColor() {" + " return vec4(vec3(1.0 - texture(pyramidMap, uv).x * 0.01), 1.0);" + //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" + " }" +}; + +static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(vec3(texture(occlusionMap, uv).x), 1.0);" + // When drawing color " return vec4(vec3(texture(occlusionMap, uv).xyz), 1.0);" + // when drawing normal " return vec4(normalize(texture(occlusionMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);" + " }" +}; +static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(vec3(texture(occlusionBlurredMap, uv).x), 1.0);" + " }" +}; + static const std::string DEFAULT_CUSTOM_SHADER { "vec4 getFragmentColor() {" " return vec4(1.0, 0.0, 0.0, 1.0);" @@ -113,8 +133,6 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Modes mode, std::string cus switch (mode) { case DiffuseMode: return DEFAULT_DIFFUSE_SHADER; - case AlphaMode: - return DEFAULT_ALPHA_SHADER; case SpecularMode: return DEFAULT_SPECULAR_SHADER; case RoughnessMode: @@ -127,6 +145,12 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Modes mode, std::string cus return DEFAULT_LIGHTING_SHADER; case ShadowMode: return DEFAULT_SHADOW_SHADER; + case PyramidDepthMode: + return DEFAULT_PYRAMID_DEPTH_SHADER; + case AmbientOcclusionMode: + return DEFAULT_AMBIENT_OCCLUSION_SHADER; + case AmbientOcclusionBlurredMode: + return DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER; case CustomMode: return getFileContent(customFile, DEFAULT_CUSTOM_SHADER); } @@ -175,6 +199,9 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Modes mode, std::st slotBindings.insert(gpu::Shader::Binding("depthMap", Depth)); slotBindings.insert(gpu::Shader::Binding("lightingMap", Lighting)); slotBindings.insert(gpu::Shader::Binding("shadowMap", Shadow)); + slotBindings.insert(gpu::Shader::Binding("pyramidMap", Pyramid)); + slotBindings.insert(gpu::Shader::Binding("occlusionMap", AmbientOcclusion)); + slotBindings.insert(gpu::Shader::Binding("occlusionBlurredMap", AmbientOcclusionBlurred)); gpu::Shader::makeProgram(*program, slotBindings); auto pipeline = gpu::Pipeline::create(program, std::make_shared()); @@ -231,7 +258,10 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren batch.setResourceTexture(Depth, framebufferCache->getPrimaryDepthTexture()); batch.setResourceTexture(Lighting, framebufferCache->getLightingTexture()); batch.setResourceTexture(Shadow, lightStage.lights[0]->shadow.framebuffer->getDepthStencilBuffer()); - + batch.setResourceTexture(Pyramid, framebufferCache->getDepthPyramidTexture()); + batch.setResourceTexture(AmbientOcclusion, framebufferCache->getOcclusionTexture()); + batch.setResourceTexture(AmbientOcclusionBlurred, framebufferCache->getOcclusionBlurredTexture()); + const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); const glm::vec2 bottomLeft(renderContext->_deferredDebugSize.x, renderContext->_deferredDebugSize.y); const glm::vec2 topRight(renderContext->_deferredDebugSize.z, renderContext->_deferredDebugSize.w); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index eba48cd0c3..ef0fe512b2 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -27,14 +27,15 @@ public: private: enum Modes : uint8_t { DiffuseMode = 0, - AlphaMode, SpecularMode, RoughnessMode, NormalMode, DepthMode, LightingMode, ShadowMode, - + PyramidDepthMode, + AmbientOcclusionMode, + AmbientOcclusionBlurredMode, CustomMode // Needs to stay last }; struct CustomPipeline { diff --git a/libraries/render-utils/src/DeferredBuffer.slh b/libraries/render-utils/src/DeferredBuffer.slh index 18606f2525..3f9aef6dd1 100755 --- a/libraries/render-utils/src/DeferredBuffer.slh +++ b/libraries/render-utils/src/DeferredBuffer.slh @@ -24,6 +24,9 @@ uniform sampler2D specularMap; // the depth texture uniform sampler2D depthMap; +// the obscurance texture +uniform sampler2D obscuranceMap; + // the lighting texture uniform sampler2D lightingMap; @@ -68,17 +71,21 @@ struct DeferredFragment { vec4 position; vec3 normal; vec3 diffuse; - float opacity; + float obscurance; vec3 specular; float gloss; + int mode; }; +const int LIGHT_MAPPED = 1; + DeferredFragment unpackDeferredFragment(DeferredTransform deferredTransform, vec2 texcoord) { DeferredFragment frag; frag.depthVal = texture(depthMap, texcoord).r; frag.normalVal = texture(normalMap, texcoord); frag.diffuseVal = texture(diffuseMap, texcoord); frag.specularVal = texture(specularMap, texcoord); + frag.obscurance = texture(obscuranceMap, texcoord).x; if (getStereoMode(deferredTransform)) { if (texcoord.x > 0.5) { @@ -88,14 +95,19 @@ DeferredFragment unpackDeferredFragment(DeferredTransform deferredTransform, vec } frag.position = evalEyePositionFromZ(deferredTransform, frag.depthVal, texcoord); - // Unpack the normal from the map + // Unpack the normal from the map frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0)); + + frag.mode = 0; + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + frag.mode = LIGHT_MAPPED; + } frag.diffuse = frag.diffuseVal.xyz; - frag.opacity = frag.diffuseVal.w; frag.specular = frag.specularVal.xyz; frag.gloss = frag.specularVal.w; + return frag; } diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index ca46ad0ebf..75fe187e46 100755 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -70,7 +70,7 @@ uniform SphericalHarmonics ambientSphere; <@include model/Light.slh@> <@func declareEvalAmbientGlobalColor()@> -vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { +vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { // Need the light now Light light = getLight(); @@ -79,11 +79,11 @@ vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 posit vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec3 color = diffuse.rgb * getLightColor(light) * getLightAmbientIntensity(light); + vec3 color = diffuse.rgb * getLightColor(light) * obscurance * getLightAmbientIntensity(light); vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); - color += vec3(diffuse * shading.w + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + color += vec3(diffuse * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); return color; } @@ -93,7 +93,7 @@ vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 posit <$declareSphericalHarmonics()$> -vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { +vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { // Need the light now Light light = getLight(); @@ -102,11 +102,11 @@ vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 vec3 fragEyeDir = normalize(fragEyeVector.xyz); vec3 ambientNormal = fragNormal.xyz; - vec3 color = diffuse.rgb * evalSphericalLight(ambientSphere, ambientNormal).xyz * getLightAmbientIntensity(light); + vec3 color = diffuse.rgb * evalSphericalLight(ambientSphere, ambientNormal).xyz * obscurance * getLightAmbientIntensity(light); vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); - color += vec3(diffuse * shading.w + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + color += vec3(diffuse * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); return color; } @@ -117,7 +117,7 @@ vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 <$declareSkyboxMap()$> <$declareSphericalHarmonics()$> -vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { +vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { // Need the light now Light light = getLight(); @@ -125,18 +125,18 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, vec3 positi vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec3 color = diffuse.rgb * evalSphericalLight(ambientSphere, fragNormal).xyz * getLightAmbientIntensity(light); + vec3 color = diffuse.rgb * evalSphericalLight(ambientSphere, fragNormal).xyz * obscurance * getLightAmbientIntensity(light); vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); - color += vec3(diffuse * shading.w + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + color += vec3(diffuse * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); return color; } <@endfunc@> <@func declareEvalLightmappedColor()@> -vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, vec3 normal, vec3 diffuse, vec3 lightmap) { +vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 diffuse, vec3 lightmap) { Light light = getLight(); @@ -156,7 +156,7 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, vec3 normal, // ambient is a tiny percentage of the lightmap and only when in the shadow vec3 ambientLight = (1 - lightAttenuation) * lightmap * getLightAmbientIntensity(light); - return diffuse * (ambientLight + diffuseLight); + return obscurance * diffuse * (ambientLight + diffuseLight); } <@endfunc@> diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8d022c93ee..723eacd6be 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -42,13 +42,21 @@ struct LightLocations { int radius; int ambientSphere; int lightBufferUnit; - int atmosphereBufferUnit; int texcoordMat; int coneParam; int deferredTransformBuffer; int shadowTransformBuffer; }; +enum { + DEFERRED_BUFFER_COLOR_UNIT = 0, + DEFERRED_BUFFER_NORMAL_UNIT = 1, + DEFERRED_BUFFER_EMISSIVE_UNIT = 2, + DEFERRED_BUFFER_DEPTH_UNIT = 3, + DEFERRED_BUFFER_OBSCURANCE_UNIT = 4, + SHADOW_MAP_UNIT = 5, + SKYBOX_MAP_UNIT = 6, +}; static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); void DeferredLightingEffect::init() { @@ -153,7 +161,8 @@ void DeferredLightingEffect::prepare(RenderArgs* args) { }); } -void DeferredLightingEffect::render(RenderArgs* args) { +void DeferredLightingEffect::render(const render::RenderContextPointer& renderContext) { + auto args = renderContext->getArgs(); gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { // Allocate the parameters buffer used by all the deferred shaders @@ -168,6 +177,7 @@ void DeferredLightingEffect::render(RenderArgs* args) { // perform deferred lighting, rendering to free fbo auto framebufferCache = DependencyManager::get(); + auto textureCache = DependencyManager::get(); QSize framebufferSize = framebufferCache->getFrameBufferSize(); @@ -179,16 +189,23 @@ void DeferredLightingEffect::render(RenderArgs* args) { batch.setStateScissorRect(args->_viewport); // BInd the G-Buffer surfaces - batch.setResourceTexture(0, framebufferCache->getDeferredColorTexture()); - batch.setResourceTexture(1, framebufferCache->getDeferredNormalTexture()); - batch.setResourceTexture(2, framebufferCache->getDeferredSpecularTexture()); - batch.setResourceTexture(3, framebufferCache->getPrimaryDepthTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, framebufferCache->getDeferredColorTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, framebufferCache->getDeferredNormalTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, framebufferCache->getDeferredSpecularTexture()); + batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, framebufferCache->getPrimaryDepthTexture()); + + // need to assign the white texture if ao is off + if (renderContext->getOcclusionStatus()) { + batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, framebufferCache->getOcclusionTexture()); + } else { + batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, textureCache->getWhiteTexture()); + } assert(_lightStage.lights.size() > 0); const auto& globalShadow = _lightStage.lights[0]->shadow; // Bind the shadow buffer - batch.setResourceTexture(4, globalShadow.map); + batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow.map); // THe main viewport is assumed to be the mono viewport (or the 2 stereo faces side by side within that viewport) auto monoViewport = args->_viewport; @@ -339,16 +356,13 @@ void DeferredLightingEffect::render(RenderArgs* args) { } if (useSkyboxCubemap) { - batch.setResourceTexture(5, _skybox->getCubemap()); + batch.setResourceTexture(SKYBOX_MAP_UNIT, _skybox->getCubemap()); } if (locations->lightBufferUnit >= 0) { batch.setUniformBuffer(locations->lightBufferUnit, globalLight->getSchemaBuffer()); } - - if (_atmosphere && (locations->atmosphereBufferUnit >= 0)) { - batch.setUniformBuffer(locations->atmosphereBufferUnit, _atmosphere->getDataBuffer()); - } + } { @@ -361,7 +375,7 @@ void DeferredLightingEffect::render(RenderArgs* args) { } if (useSkyboxCubemap) { - batch.setResourceTexture(5, nullptr); + batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr); } } @@ -477,10 +491,14 @@ void DeferredLightingEffect::render(RenderArgs* args) { } // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target - batch.setResourceTexture(0, nullptr); - batch.setResourceTexture(1, nullptr); - batch.setResourceTexture(2, nullptr); - batch.setResourceTexture(3, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, nullptr); + batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, nullptr); + batch.setResourceTexture(SHADOW_MAP_UNIT, nullptr); + batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr); + batch.setUniformBuffer(_directionalLightLocations->deferredTransformBuffer, nullptr); }); @@ -505,18 +523,17 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), 0)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), 2)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), 3)); - slotBindings.insert(gpu::Shader::Binding(std::string("shadowMap"), 4)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), 5)); + slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), DEFERRED_BUFFER_COLOR_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), DEFERRED_BUFFER_NORMAL_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), DEFERRED_BUFFER_EMISSIVE_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DEFERRED_BUFFER_DEPTH_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("obscuranceMap"), DEFERRED_BUFFER_OBSCURANCE_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("shadowMap"), SHADOW_MAP_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), SKYBOX_MAP_UNIT)); static const int LIGHT_GPU_SLOT = 3; - static const int ATMOSPHERE_GPU_SLOT = 4; static const int DEFERRED_TRANSFORM_BUFFER_SLOT = 2; slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), LIGHT_GPU_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("atmosphereBufferUnit"), ATMOSPHERE_GPU_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("deferredTransformBuffer"), DEFERRED_TRANSFORM_BUFFER_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); @@ -528,7 +545,6 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo locations->coneParam = program->getUniforms().findLocation("coneParam"); locations->lightBufferUnit = program->getBuffers().findLocation("lightBuffer"); - locations->atmosphereBufferUnit = program->getBuffers().findLocation("atmosphereBufferUnit"); locations->deferredTransformBuffer = program->getBuffers().findLocation("deferredTransformBuffer"); locations->shadowTransformBuffer = program->getBuffers().findLocation("shadowTransformBuffer"); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index b254052fb2..82b9eeadba 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -21,6 +21,8 @@ #include "model/Stage.h" #include "model/Geometry.h" +#include "render/Context.h" + #include "LightStage.h" class RenderArgs; @@ -42,14 +44,13 @@ public: float intensity = 0.5f, const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI); void prepare(RenderArgs* args); - void render(RenderArgs* args); + void render(const render::RenderContextPointer& renderContext); void setupTransparent(RenderArgs* args, int lightBufferUnit); // update global lighting void setAmbientLightMode(int preset); void setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity, float ambientIntensity); - void setGlobalAtmosphere(const model::AtmospherePointer& atmosphere) { _atmosphere = atmosphere; } void setGlobalSkybox(const model::SkyboxPointer& skybox); const LightStage& getLightStage() { return _lightStage; } @@ -94,7 +95,6 @@ private: std::vector _spotLights; int _ambientLightMode = 0; - model::AtmospherePointer _atmosphere; model::SkyboxPointer _skybox; // Class describing the uniform buffer with all the parameters common to the deferred shaders diff --git a/libraries/render-utils/src/Environment.cpp b/libraries/render-utils/src/Environment.cpp deleted file mode 100644 index f7327a2b72..0000000000 --- a/libraries/render-utils/src/Environment.cpp +++ /dev/null @@ -1,256 +0,0 @@ -// -// Environment.cpp -// interface/src -// -// Created by Andrzej Kapolka on 5/6/13. -// Copyright 2013 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 -#include -#include - -#include "GeometryCache.h" -#include -#include -#include -#include -#include -#include - -#include "Environment.h" - -#include "SkyFromSpace_vert.h" -#include "SkyFromSpace_frag.h" -#include "SkyFromAtmosphere_vert.h" -#include "SkyFromAtmosphere_frag.h" - -Environment::Environment() - : _initialized(false) { -} - -Environment::~Environment() { -} - -void Environment::init() { - if (_initialized) { - return; - } - - setupAtmosphereProgram(SkyFromSpace_vert, SkyFromSpace_frag, _skyFromSpaceProgram, _skyFromSpaceUniformLocations); - setupAtmosphereProgram(SkyFromAtmosphere_vert, SkyFromAtmosphere_frag, _skyFromAtmosphereProgram, _skyFromAtmosphereUniformLocations); - - // start off with a default-constructed environment data - _data[QUuid()][0]; - - _initialized = true; -} - -void Environment::setupAtmosphereProgram(const char* vertSource, const char* fragSource, gpu::PipelinePointer& pipeline, int* locations) { - - auto VS = gpu::Shader::createVertex(std::string(vertSource)); - auto PS = gpu::Shader::createPixel(std::string(fragSource)); - - gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - auto state = std::make_shared(); - - state->setCullMode(gpu::State::CULL_NONE); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - - pipeline = gpu::Pipeline::create(program, state); - - locations[CAMERA_POS_LOCATION] = program->getUniforms().findLocation("v3CameraPos"); - locations[LIGHT_POS_LOCATION] = program->getUniforms().findLocation("v3LightPos"); - locations[INV_WAVELENGTH_LOCATION] = program->getUniforms().findLocation("v3InvWavelength"); - locations[CAMERA_HEIGHT2_LOCATION] = program->getUniforms().findLocation("fCameraHeight2"); - locations[OUTER_RADIUS_LOCATION] = program->getUniforms().findLocation("fOuterRadius"); - locations[OUTER_RADIUS2_LOCATION] = program->getUniforms().findLocation("fOuterRadius2"); - locations[INNER_RADIUS_LOCATION] = program->getUniforms().findLocation("fInnerRadius"); - locations[KR_ESUN_LOCATION] = program->getUniforms().findLocation("fKrESun"); - locations[KM_ESUN_LOCATION] = program->getUniforms().findLocation("fKmESun"); - locations[KR_4PI_LOCATION] = program->getUniforms().findLocation("fKr4PI"); - locations[KM_4PI_LOCATION] = program->getUniforms().findLocation("fKm4PI"); - locations[SCALE_LOCATION] = program->getUniforms().findLocation("fScale"); - locations[SCALE_DEPTH_LOCATION] = program->getUniforms().findLocation("fScaleDepth"); - locations[SCALE_OVER_SCALE_DEPTH_LOCATION] = program->getUniforms().findLocation("fScaleOverScaleDepth"); - locations[G_LOCATION] = program->getUniforms().findLocation("g"); - locations[G2_LOCATION] = program->getUniforms().findLocation("g2"); -} - -void Environment::resetToDefault() { - _data.clear(); - _data[QUuid()][0]; -} - -void Environment::renderAtmospheres(gpu::Batch& batch, ViewFrustum& viewFrustum) { - // get the lock for the duration of the call - QMutexLocker locker(&_mutex); - - if (_environmentIsOverridden) { - renderAtmosphere(batch, viewFrustum, _overrideData); - } else { - foreach (const ServerData& serverData, _data) { - // TODO: do something about EnvironmentData - foreach (const EnvironmentData& environmentData, serverData) { - renderAtmosphere(batch, viewFrustum, environmentData); - } - } - } -} - -EnvironmentData Environment::getClosestData(const glm::vec3& position) { - if (_environmentIsOverridden) { - return _overrideData; - } - - // get the lock for the duration of the call - QMutexLocker locker(&_mutex); - - EnvironmentData closest; - float closestDistance = FLT_MAX; - foreach (const ServerData& serverData, _data) { - foreach (const EnvironmentData& environmentData, serverData) { - float distance = glm::distance(position, environmentData.getAtmosphereCenter(position)) - - environmentData.getAtmosphereOuterRadius(); - if (distance < closestDistance) { - closest = environmentData; - closestDistance = distance; - } - } - } - return closest; -} - - -// NOTE: Deprecated - I'm leaving this in for now, but it's not actually used. I made it private -// so that if anyone wants to start using this in the future they will consider how to make it -// work with new physics systems. -glm::vec3 Environment::getGravity (const glm::vec3& position) { - // - // 'Default' gravity pulls you downward in Y when you are near the X/Z plane - const glm::vec3 DEFAULT_GRAVITY(0.0f, -1.0f, 0.0f); - glm::vec3 gravity(DEFAULT_GRAVITY); - float DEFAULT_SURFACE_RADIUS = 30.0f; - float gravityStrength; - - // Weaken gravity with height - if (position.y > 0.0f) { - gravityStrength = 1.0f / powf((DEFAULT_SURFACE_RADIUS + position.y) / DEFAULT_SURFACE_RADIUS, 2.0f); - gravity *= gravityStrength; - } - - // get the lock for the duration of the call - QMutexLocker locker(&_mutex); - - foreach (const ServerData& serverData, _data) { - foreach (const EnvironmentData& environmentData, serverData) { - glm::vec3 vector = environmentData.getAtmosphereCenter(position) - position; - float surfaceRadius = environmentData.getAtmosphereInnerRadius(); - if (glm::length(vector) <= surfaceRadius) { - // At or inside a planet, gravity is as set for the planet - gravity += glm::normalize(vector) * environmentData.getGravity(); - } else { - // Outside a planet, the gravity falls off with distance - gravityStrength = 1.0f / powf(glm::length(vector) / surfaceRadius, 2.0f); - gravity += glm::normalize(vector) * environmentData.getGravity() * gravityStrength; - } - } - } - - return gravity; -} - -bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, - float radius, glm::vec3& penetration) { - // collide with the "floor" - bool found = findCapsulePlanePenetration(start, end, radius, glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), penetration); - - glm::vec3 middle = (start + end) * 0.5f; - - // get the lock for the duration of the call - QMutexLocker locker(&_mutex); - - foreach (const ServerData& serverData, _data) { - foreach (const EnvironmentData& environmentData, serverData) { - if (environmentData.getGravity() == 0.0f) { - continue; // don't bother colliding with gravity-less environments - } - glm::vec3 environmentPenetration; - if (findCapsuleSpherePenetration(start, end, radius, environmentData.getAtmosphereCenter(middle), - environmentData.getAtmosphereInnerRadius(), environmentPenetration)) { - penetration = addPenetrations(penetration, environmentPenetration); - found = true; - } - } - } - return found; -} - -void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data) { - glm::vec3 center = data.getAtmosphereCenter(); - - // transform the model transform to the center of our atmosphere - Transform transform; - transform.setTranslation(center); - batch.setModelTransform(transform); - - // Make sure our view and projection transforms are correct for our viewFrustum - Transform viewTransform; - viewFrustum.evalViewTransform(viewTransform); - batch.setViewTransform(viewTransform); - - glm::mat4 projMat; - viewFrustum.evalProjectionMatrix(projMat); - batch.setProjectionTransform(projMat); - - glm::vec3 relativeCameraPos = viewFrustum.getPosition() - center; - float height = glm::length(relativeCameraPos); - - // use the appropriate shader depending on whether we're inside or outside - int* locations; - if (height < data.getAtmosphereOuterRadius()) { - batch.setPipeline(_skyFromAtmosphereProgram); - locations = _skyFromAtmosphereUniformLocations; - } else { - batch.setPipeline(_skyFromSpaceProgram); - locations = _skyFromSpaceUniformLocations; - } - - - // the constants here are from Sean O'Neil's GPU Gems entry - // (http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html), GameEngine.cpp - batch._glUniform3f(locations[CAMERA_POS_LOCATION], relativeCameraPos.x, relativeCameraPos.y, relativeCameraPos.z); - glm::vec3 lightDirection = glm::normalize(data.getSunLocation()); - batch._glUniform3f(locations[LIGHT_POS_LOCATION], lightDirection.x, lightDirection.y, lightDirection.z); - batch._glUniform3f(locations[INV_WAVELENGTH_LOCATION], - 1 / powf(data.getScatteringWavelengths().r, 4.0f), - 1 / powf(data.getScatteringWavelengths().g, 4.0f), - 1 / powf(data.getScatteringWavelengths().b, 4.0f)); - batch._glUniform1f(locations[CAMERA_HEIGHT2_LOCATION], height * height); - batch._glUniform1f(locations[OUTER_RADIUS_LOCATION], data.getAtmosphereOuterRadius()); - batch._glUniform1f(locations[OUTER_RADIUS2_LOCATION], data.getAtmosphereOuterRadius() * data.getAtmosphereOuterRadius()); - batch._glUniform1f(locations[INNER_RADIUS_LOCATION], data.getAtmosphereInnerRadius()); - batch._glUniform1f(locations[KR_ESUN_LOCATION], data.getRayleighScattering() * data.getSunBrightness()); - batch._glUniform1f(locations[KM_ESUN_LOCATION], data.getMieScattering() * data.getSunBrightness()); - batch._glUniform1f(locations[KR_4PI_LOCATION], data.getRayleighScattering() * 4.0f * PI); - batch._glUniform1f(locations[KM_4PI_LOCATION], data.getMieScattering() * 4.0f * PI); - batch._glUniform1f(locations[SCALE_LOCATION], 1.0f / (data.getAtmosphereOuterRadius() - data.getAtmosphereInnerRadius())); - batch._glUniform1f(locations[SCALE_DEPTH_LOCATION], 0.25f); - batch._glUniform1f(locations[SCALE_OVER_SCALE_DEPTH_LOCATION], - (1.0f / (data.getAtmosphereOuterRadius() - data.getAtmosphereInnerRadius())) / 0.25f); - batch._glUniform1f(locations[G_LOCATION], -0.990f); - batch._glUniform1f(locations[G2_LOCATION], -0.990f * -0.990f); - - batch._glColor4f(1.0f, 0.0f, 0.0f, 0.5f); - DependencyManager::get()->renderSphere(batch); //Draw a unit sphere -} diff --git a/libraries/render-utils/src/Environment.h b/libraries/render-utils/src/Environment.h deleted file mode 100644 index af5a3c3df5..0000000000 --- a/libraries/render-utils/src/Environment.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// Environment.h -// interface/src -// -// Created by Andrzej Kapolka on 5/6/13. -// Copyright 2013 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_Environment_h -#define hifi_Environment_h - -#include -#include - -#include -#include - -#include - -class ViewFrustum; - -class Environment { -public: - Environment(); - ~Environment(); - - void init(); - void resetToDefault(); - void renderAtmospheres(gpu::Batch& batch, ViewFrustum& viewFrustum); - - void override(const EnvironmentData& overrideData) { _overrideData = overrideData; _environmentIsOverridden = true; } - void endOverride() { _environmentIsOverridden = false; } - - EnvironmentData getClosestData(const glm::vec3& position); - -private: - glm::vec3 getGravity (const glm::vec3& position); // NOTE: Deprecated - bool findCapsulePenetration(const glm::vec3& start, - const glm::vec3& end, float radius, glm::vec3& penetration); // NOTE: Deprecated - - void renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data); - - bool _initialized; - - enum { - CAMERA_POS_LOCATION, - LIGHT_POS_LOCATION, - INV_WAVELENGTH_LOCATION, - CAMERA_HEIGHT2_LOCATION, - OUTER_RADIUS_LOCATION, - OUTER_RADIUS2_LOCATION, - INNER_RADIUS_LOCATION, - KR_ESUN_LOCATION, - KM_ESUN_LOCATION, - KR_4PI_LOCATION, - KM_4PI_LOCATION, - SCALE_LOCATION, - SCALE_DEPTH_LOCATION, - SCALE_OVER_SCALE_DEPTH_LOCATION, - G_LOCATION, - G2_LOCATION, - LOCATION_COUNT - }; - - void setupAtmosphereProgram(const char* vertSource, const char* fragSource, gpu::PipelinePointer& pipelineProgram, int* locations); - - - gpu::PipelinePointer _skyFromAtmosphereProgram; - gpu::PipelinePointer _skyFromSpaceProgram; - int _skyFromAtmosphereUniformLocations[LOCATION_COUNT]; - int _skyFromSpaceUniformLocations[LOCATION_COUNT]; - - typedef QHash ServerData; - - QHash _data; - EnvironmentData _overrideData; - bool _environmentIsOverridden = false; - - QMutex _mutex; -}; - -#endif // hifi_Environment_h diff --git a/libraries/render-utils/src/FramebufferCache.cpp b/libraries/render-utils/src/FramebufferCache.cpp index 4b2919e931..7948dfcefe 100644 --- a/libraries/render-utils/src/FramebufferCache.cpp +++ b/libraries/render-utils/src/FramebufferCache.cpp @@ -45,6 +45,12 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) { _cachedFramebuffers.clear(); _lightingTexture.reset(); _lightingFramebuffer.reset(); + _depthPyramidFramebuffer.reset(); + _depthPyramidTexture.reset(); + _occlusionFramebuffer.reset(); + _occlusionTexture.reset(); + _occlusionBlurredFramebuffer.reset(); + _occlusionBlurredTexture.reset(); } } @@ -96,6 +102,42 @@ void FramebufferCache::createPrimaryFramebuffer() { _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + + // For AO: + auto pointMipSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_POINT); + _depthPyramidTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, pointMipSampler)); + _depthPyramidFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _depthPyramidFramebuffer->setRenderBuffer(0, _depthPyramidTexture); + _depthPyramidFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + + resizeAmbientOcclusionBuffers(); +} + + +void FramebufferCache::resizeAmbientOcclusionBuffers() { + _occlusionFramebuffer.reset(); + _occlusionTexture.reset(); + _occlusionBlurredFramebuffer.reset(); + _occlusionBlurredTexture.reset(); + + + auto width = _frameBufferSize.width() >> _AOResolutionLevel; + auto height = _frameBufferSize.height() >> _AOResolutionLevel; + auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGB); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + + _occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + _occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture); + _occlusionFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + _occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + _occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture); + _occlusionBlurredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); } gpu::FramebufferPointer FramebufferCache::getPrimaryFramebuffer() { @@ -189,3 +231,54 @@ gpu::FramebufferPointer FramebufferCache::getSelfieFramebuffer() { } return _selfieFramebuffer; } + +gpu::FramebufferPointer FramebufferCache::getDepthPyramidFramebuffer() { + if (!_depthPyramidFramebuffer) { + createPrimaryFramebuffer(); + } + return _depthPyramidFramebuffer; +} + +gpu::TexturePointer FramebufferCache::getDepthPyramidTexture() { + if (!_depthPyramidTexture) { + createPrimaryFramebuffer(); + } + return _depthPyramidTexture; +} + +void FramebufferCache::setAmbientOcclusionResolutionLevel(int level) { + const int MAX_AO_RESOLUTION_LEVEL = 4; + level = std::max(0, std::min(level, MAX_AO_RESOLUTION_LEVEL)); + if (level != _AOResolutionLevel) { + _AOResolutionLevel = level; + resizeAmbientOcclusionBuffers(); + } +} + +gpu::FramebufferPointer FramebufferCache::getOcclusionFramebuffer() { + if (!_occlusionFramebuffer) { + resizeAmbientOcclusionBuffers(); + } + return _occlusionFramebuffer; +} + +gpu::TexturePointer FramebufferCache::getOcclusionTexture() { + if (!_occlusionTexture) { + resizeAmbientOcclusionBuffers(); + } + return _occlusionTexture; +} + +gpu::FramebufferPointer FramebufferCache::getOcclusionBlurredFramebuffer() { + if (!_occlusionBlurredFramebuffer) { + resizeAmbientOcclusionBuffers(); + } + return _occlusionBlurredFramebuffer; +} + +gpu::TexturePointer FramebufferCache::getOcclusionBlurredTexture() { + if (!_occlusionBlurredTexture) { + resizeAmbientOcclusionBuffers(); + } + return _occlusionBlurredTexture; +} \ No newline at end of file diff --git a/libraries/render-utils/src/FramebufferCache.h b/libraries/render-utils/src/FramebufferCache.h index 911c6f98dc..7c7c309572 100644 --- a/libraries/render-utils/src/FramebufferCache.h +++ b/libraries/render-utils/src/FramebufferCache.h @@ -44,7 +44,16 @@ public: gpu::TexturePointer getDeferredNormalTexture(); gpu::TexturePointer getDeferredSpecularTexture(); + gpu::FramebufferPointer getDepthPyramidFramebuffer(); + gpu::TexturePointer getDepthPyramidTexture(); + + void setAmbientOcclusionResolutionLevel(int level); + gpu::FramebufferPointer getOcclusionFramebuffer(); + gpu::TexturePointer getOcclusionTexture(); + gpu::FramebufferPointer getOcclusionBlurredFramebuffer(); + gpu::TexturePointer getOcclusionBlurredTexture(); + gpu::TexturePointer getLightingTexture(); gpu::FramebufferPointer getLightingFramebuffer(); @@ -83,7 +92,22 @@ private: gpu::FramebufferPointer _selfieFramebuffer; + gpu::FramebufferPointer _depthPyramidFramebuffer; + gpu::TexturePointer _depthPyramidTexture; + + + gpu::FramebufferPointer _occlusionFramebuffer; + gpu::TexturePointer _occlusionTexture; + + gpu::FramebufferPointer _occlusionBlurredFramebuffer; + gpu::TexturePointer _occlusionBlurredTexture; + QSize _frameBufferSize{ 100, 100 }; + int _AOResolutionLevel = 1; // AO perform at half res + + // Resize/reallocate the buffers used for AO + // the size of the AO buffers is scaled by the AOResolutionScale; + void resizeAmbientOcclusionBuffers(); }; #endif // hifi_FramebufferCache_h diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 7bf40b53d3..ac6f82abea 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -64,11 +64,10 @@ void PrepareDeferred::run(const SceneContextPointer& sceneContext, const RenderC } void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { - DependencyManager::get()->render(renderContext->getArgs()); + DependencyManager::get()->render(renderContext); } void ToneMappingDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { - PerformanceTimer perfTimer("ToneMappingDeferred"); _toneMappingEffect.render(renderContext->getArgs()); } @@ -108,17 +107,17 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) : Task() { // Use Stencil and start drawing background in Lighting buffer addJob("DrawBackgroundDeferred"); + // AO job + addJob("AmbientOcclusion"); + _jobs.back().setEnabled(false); + _occlusionJobIndex = (int)_jobs.size() - 1; + // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. addJob("DrawLight", cullFunctor); // DeferredBuffer is complete, now let's shade it into the LightingBuffer addJob("RenderDeferred"); - // AO job, to be revisited - addJob("AmbientOcclusion"); - _occlusionJobIndex = (int)_jobs.size() - 1; - enableJob(_occlusionJobIndex, false); - // AA job to be revisited addJob("Antialiasing"); _antialiasingJobIndex = (int)_jobs.size() - 1; @@ -173,6 +172,20 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend setDrawHitEffect(renderContext->getDrawHitEffect()); // TODO: turn on/off AO through menu item setOcclusionStatus(renderContext->getOcclusionStatus()); + + if (_occlusionJobIndex >= 0) { + _jobs[_occlusionJobIndex].edit().setResolutionLevel(renderContext->getAmbientOcclusion().resolutionLevel); + _jobs[_occlusionJobIndex].edit().setRadius(renderContext->getAmbientOcclusion().radius); + _jobs[_occlusionJobIndex].edit().setLevel(renderContext->getAmbientOcclusion().level); + _jobs[_occlusionJobIndex].edit().setNumSamples(renderContext->getAmbientOcclusion().numSamples); + _jobs[_occlusionJobIndex].edit().setNumSpiralTurns(renderContext->getAmbientOcclusion().numSpiralTurns); + _jobs[_occlusionJobIndex].edit().setDithering(renderContext->getAmbientOcclusion().ditheringEnabled); + _jobs[_occlusionJobIndex].edit().setFalloffBias(renderContext->getAmbientOcclusion().falloffBias); + _jobs[_occlusionJobIndex].edit().setEdgeSharpness(renderContext->getAmbientOcclusion().edgeSharpness); + _jobs[_occlusionJobIndex].edit().setBlurRadius(renderContext->getAmbientOcclusion().blurRadius); + _jobs[_occlusionJobIndex].edit().setBlurDeviation(renderContext->getAmbientOcclusion().blurDeviation); + } + setAntialiasingStatus(renderContext->getFxaaStatus()); setToneMappingExposure(renderContext->getTone().exposure); setToneMappingToneCurve(renderContext->getTone().toneCurve); @@ -181,10 +194,17 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend // TODO: For now, lighting is controlled through a singleton, so it is distinct DependencyManager::get()->setShadowMapStatus(renderContext->getShadowMapStatus()); + renderContext->getArgs()->_context->syncCache(); + for (auto job : _jobs) { job.run(sceneContext, renderContext); } + if (_occlusionJobIndex >= 0 && renderContext->getOcclusionStatus()) { + renderContext->getAmbientOcclusion().gpuTime = _jobs[_occlusionJobIndex].edit().getGPUTime(); + } else { + renderContext->getAmbientOcclusion().gpuTime = 0.0; + } }; void DrawOpaqueDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems) { diff --git a/libraries/render-utils/src/RenderScriptingInterface.cpp b/libraries/render-utils/src/RenderScriptingInterface.cpp index a99dc814d5..6b3fedd97f 100644 --- a/libraries/render-utils/src/RenderScriptingInterface.cpp +++ b/libraries/render-utils/src/RenderScriptingInterface.cpp @@ -42,11 +42,15 @@ QString RenderScripting::Tone::getCurve() const { render::RenderContext RenderScriptingInterface::getRenderContext() { render::RenderContext::ItemsConfig items{ *_opaque, *_transparent, *_overlay3D }; - return render::RenderContext{ items, *_tone, _drawStatus, _drawHitEffect, _deferredDebugSize, _deferredDebugMode }; + return render::RenderContext{ items, *_tone, *_ambientOcclusion, _drawStatus, _drawHitEffect, _deferredDebugSize, _deferredDebugMode }; } void RenderScriptingInterface::setItemCounts(const render::RenderContext::ItemsConfig& items) { _opaque->setCounts(items.opaque); _transparent->setCounts(items.transparent); _overlay3D->setCounts(items.overlay3D); +} + +void RenderScriptingInterface::setJobGPUTimes(double aoTime) { + _ambientOcclusion->gpuTime = aoTime; } \ No newline at end of file diff --git a/libraries/render-utils/src/RenderScriptingInterface.h b/libraries/render-utils/src/RenderScriptingInterface.h index 08226ef6df..947240fea6 100644 --- a/libraries/render-utils/src/RenderScriptingInterface.h +++ b/libraries/render-utils/src/RenderScriptingInterface.h @@ -65,6 +65,24 @@ namespace RenderScripting { void setCurve(const QString& curve); }; using TonePointer = std::unique_ptr; + + class AmbientOcclusion : public QObject, public render::RenderContext::AmbientOcclusion { + Q_OBJECT + + public: + Q_PROPERTY(int resolutionLevel MEMBER resolutionLevel) + Q_PROPERTY(float radius MEMBER radius) + Q_PROPERTY(float level MEMBER level) + Q_PROPERTY(int numSamples MEMBER numSamples) + Q_PROPERTY(float numSpiralTurns MEMBER numSpiralTurns) + Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled) + Q_PROPERTY(float falloffBias MEMBER falloffBias) + Q_PROPERTY(float edgeSharpness MEMBER edgeSharpness) + Q_PROPERTY(int blurRadius MEMBER blurRadius) + Q_PROPERTY(float blurDeviation MEMBER blurDeviation) + Q_PROPERTY(double gpuTime MEMBER gpuTime) + }; + using AmbientOcclusionPointer = std::unique_ptr; }; class RenderScriptingInterface : public QObject, public Dependency { @@ -77,7 +95,8 @@ class RenderScriptingInterface : public QObject, public Dependency { Q_PROPERTY(RenderScripting::ItemCounter* overlay3D READ getOverlay3D) Q_PROPERTY(RenderScripting::Tone* tone READ getTone) - + Q_PROPERTY(RenderScripting::AmbientOcclusion* ambientOcclusion READ getAmbientOcclusion) + Q_PROPERTY(int displayItemStatus MEMBER _drawStatus) Q_PROPERTY(bool displayHitEffect MEMBER _drawHitEffect) @@ -87,6 +106,9 @@ class RenderScriptingInterface : public QObject, public Dependency { render::RenderContext getRenderContext(); void setItemCounts(const render::RenderContext::ItemsConfig& items); + // FIXME: It is ugly, we need a cleaner solution + void setJobGPUTimes(double aoTime); + protected: RenderScriptingInterface(); ~RenderScriptingInterface() {}; @@ -96,12 +118,15 @@ protected: RenderScripting::ItemCounter* getOverlay3D() const { return _overlay3D.get(); } RenderScripting::Tone* getTone() const { return _tone.get(); } + RenderScripting::AmbientOcclusion* getAmbientOcclusion() const { return _ambientOcclusion.get(); } RenderScripting::ItemStatePointer _opaque = RenderScripting::ItemStatePointer{new RenderScripting::ItemState{}}; RenderScripting::ItemStatePointer _transparent = RenderScripting::ItemStatePointer{new RenderScripting::ItemState{}}; RenderScripting::ItemCounterPointer _overlay3D = RenderScripting::ItemCounterPointer{new RenderScripting::ItemCounter{}}; RenderScripting::TonePointer _tone = RenderScripting::TonePointer{ new RenderScripting::Tone{} }; + + RenderScripting::AmbientOcclusionPointer _ambientOcclusion = RenderScripting::AmbientOcclusionPointer{ new RenderScripting::AmbientOcclusion{} }; // Options int _drawStatus = 0; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 6f72c2ce12..7240fba105 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -13,6 +13,7 @@ #include +#include "render/Context.h" #include "DeferredLightingEffect.h" #include "FramebufferCache.h" @@ -118,7 +119,7 @@ RenderShadowTask::RenderShadowTask(CullFunctor cullFunctor) : Task() { addJob("RenderShadowMap", shadowShapes, shapePlumber); } -void RenderShadowTask::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { +void RenderShadowTask::run(const SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(sceneContext); RenderArgs* args = renderContext->getArgs(); diff --git a/libraries/render-utils/src/SkyFromAtmosphere.slf b/libraries/render-utils/src/SkyFromAtmosphere.slf deleted file mode 100755 index 10b39dc210..0000000000 --- a/libraries/render-utils/src/SkyFromAtmosphere.slf +++ /dev/null @@ -1,113 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> - -// -// For licensing information, see http://http.developer.nvidia.com/GPUGems/gpugems_app01.html: -// -// NVIDIA Statement on the Software -// -// The source code provided is freely distributable, so long as the NVIDIA header remains unaltered and user modifications are -// detailed. -// -// No Warranty -// -// THE SOFTWARE AND ANY OTHER MATERIALS PROVIDED BY NVIDIA ON THE ENCLOSED CD-ROM ARE PROVIDED "AS IS." NVIDIA DISCLAIMS ALL -// WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// -// Limitation of Liability -// -// NVIDIA SHALL NOT BE LIABLE TO ANY USER, DEVELOPER, DEVELOPER'S CUSTOMERS, OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH OR -// UNDER DEVELOPER FOR ANY LOSS OF PROFITS, INCOME, SAVINGS, OR ANY OTHER CONSEQUENTIAL, INCIDENTAL, SPECIAL, PUNITIVE, DIRECT -// OR INDIRECT DAMAGES (WHETHER IN AN ACTION IN CONTRACT, TORT OR BASED ON A WARRANTY), EVEN IF NVIDIA HAS BEEN ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF THE ESSENTIAL PURPOSE OF ANY -// LIMITED REMEDY. IN NO EVENT SHALL NVIDIA'S AGGREGATE LIABILITY TO DEVELOPER OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH -// OR UNDER DEVELOPER EXCEED THE AMOUNT OF MONEY ACTUALLY PAID BY DEVELOPER TO NVIDIA FOR THE SOFTWARE OR ANY OTHER MATERIALS. -// - -// -// Atmospheric scattering fragment shader -// -// Author: Sean O'Neil -// -// Copyright (c) 2004 Sean O'Neil -// - -uniform vec3 v3CameraPos; // The camera's current position -uniform vec3 v3InvWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels -uniform float fInnerRadius; // The inner (planetary) radius -uniform float fKrESun; // Kr * ESun -uniform float fKmESun; // Km * ESun -uniform float fKr4PI; // Kr * 4 * PI -uniform float fKm4PI; // Km * 4 * PI -uniform float fScale; // 1 / (fOuterRadius - fInnerRadius) -uniform float fScaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found) -uniform float fScaleOverScaleDepth; // fScale / fScaleDepth - -const int nSamples = 2; -const float fSamples = 2.0; - -uniform vec3 v3LightPos; -uniform float g; -uniform float g2; - -in vec3 position; -out vec4 outFragColor; - - -float scale(float fCos) -{ - float x = 1.0 - fCos; - return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); -} - - -void main (void) -{ - // Get the ray from the camera to the vertex, and its length (which is the far point of the ray passing through the atmosphere) - vec3 v3Pos = position; - vec3 v3Ray = v3Pos - v3CameraPos; - float fFar = length(v3Ray); - v3Ray /= fFar; - - // Calculate the ray's starting position, then calculate its scattering offset - vec3 v3Start = v3CameraPos; - float fHeight = length(v3Start); - float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight)); - float fStartAngle = dot(v3Ray, v3Start) / fHeight; - float fStartOffset = fDepth * scale(fStartAngle); - - // Initialize the scattering loop variables - //gl_FrontColor = vec4(0.0, 0.0, 0.0, 0.0); - float fSampleLength = fFar / fSamples; - float fScaledLength = fSampleLength * fScale; - vec3 v3SampleRay = v3Ray * fSampleLength; - vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; - - // Now loop through the sample rays - vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); - for(int i=0; i -<$VERSION_HEADER$> - -// -// For licensing information, see http://http.developer.nvidia.com/GPUGems/gpugems_app01.html: -// -// NVIDIA Statement on the Software -// -// The source code provided is freely distributable, so long as the NVIDIA header remains unaltered and user modifications are -// detailed. -// -// No Warranty -// -// THE SOFTWARE AND ANY OTHER MATERIALS PROVIDED BY NVIDIA ON THE ENCLOSED CD-ROM ARE PROVIDED "AS IS." NVIDIA DISCLAIMS ALL -// WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// -// Limitation of Liability -// -// NVIDIA SHALL NOT BE LIABLE TO ANY USER, DEVELOPER, DEVELOPER'S CUSTOMERS, OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH OR -// UNDER DEVELOPER FOR ANY LOSS OF PROFITS, INCOME, SAVINGS, OR ANY OTHER CONSEQUENTIAL, INCIDENTAL, SPECIAL, PUNITIVE, DIRECT -// OR INDIRECT DAMAGES (WHETHER IN AN ACTION IN CONTRACT, TORT OR BASED ON A WARRANTY), EVEN IF NVIDIA HAS BEEN ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF THE ESSENTIAL PURPOSE OF ANY -// LIMITED REMEDY. IN NO EVENT SHALL NVIDIA'S AGGREGATE LIABILITY TO DEVELOPER OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH -// OR UNDER DEVELOPER EXCEED THE AMOUNT OF MONEY ACTUALLY PAID BY DEVELOPER TO NVIDIA FOR THE SOFTWARE OR ANY OTHER MATERIALS. -// - -// -// Atmospheric scattering vertex shader -// -// Author: Sean O'Neil -// -// Copyright (c) 2004 Sean O'Neil -// - -<@include gpu/Inputs.slh@> -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> - -uniform vec3 v3CameraPos; // The camera's current position -uniform vec3 v3LightPos; // The direction vector to the light source -uniform vec3 v3InvWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels -uniform float fOuterRadius; // The outer (atmosphere) radius -uniform float fInnerRadius; // The inner (planetary) radius -uniform float fKrESun; // Kr * ESun -uniform float fKmESun; // Km * ESun -uniform float fKr4PI; // Kr * 4 * PI -uniform float fKm4PI; // Km * 4 * PI -uniform float fScale; // 1 / (fOuterRadius - fInnerRadius) -uniform float fScaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found) -uniform float fScaleOverScaleDepth; // fScale / fScaleDepth - - -const int nSamples = 2; -const float fSamples = 2.0; - -out vec3 position; - -void main(void) -{ - // Get the ray from the camera to the vertex, and its length (which is the far point of the ray passing through the atmosphere) - position = inPosition.xyz * fOuterRadius; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - vec4 v4pos = vec4(position, 1.0); - <$transformModelToClipPos(cam, obj, v4pos, gl_Position)$> -} diff --git a/libraries/render-utils/src/SkyFromSpace.slf b/libraries/render-utils/src/SkyFromSpace.slf deleted file mode 100755 index 42282fe08b..0000000000 --- a/libraries/render-utils/src/SkyFromSpace.slf +++ /dev/null @@ -1,119 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> - -// -// For licensing information, see http://http.developer.nvidia.com/GPUGems/gpugems_app01.html: -// -// NVIDIA Statement on the Software -// -// The source code provided is freely distributable, so long as the NVIDIA header remains unaltered and user modifications are -// detailed. -// -// No Warranty -// -// THE SOFTWARE AND ANY OTHER MATERIALS PROVIDED BY NVIDIA ON THE ENCLOSED CD-ROM ARE PROVIDED "AS IS." NVIDIA DISCLAIMS ALL -// WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// -// Limitation of Liability -// -// NVIDIA SHALL NOT BE LIABLE TO ANY USER, DEVELOPER, DEVELOPER'S CUSTOMERS, OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH OR -// UNDER DEVELOPER FOR ANY LOSS OF PROFITS, INCOME, SAVINGS, OR ANY OTHER CONSEQUENTIAL, INCIDENTAL, SPECIAL, PUNITIVE, DIRECT -// OR INDIRECT DAMAGES (WHETHER IN AN ACTION IN CONTRACT, TORT OR BASED ON A WARRANTY), EVEN IF NVIDIA HAS BEEN ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF THE ESSENTIAL PURPOSE OF ANY -// LIMITED REMEDY. IN NO EVENT SHALL NVIDIA'S AGGREGATE LIABILITY TO DEVELOPER OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH -// OR UNDER DEVELOPER EXCEED THE AMOUNT OF MONEY ACTUALLY PAID BY DEVELOPER TO NVIDIA FOR THE SOFTWARE OR ANY OTHER MATERIALS. -// - -// -// Atmospheric scattering fragment shader -// -// Author: Sean O'Neil -// -// Copyright (c) 2004 Sean O'Neil -// - -uniform vec3 v3CameraPos; // The camera's current position -uniform vec3 v3LightPos; // The direction vector to the light source -uniform vec3 v3InvWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels -uniform float fCameraHeight2; // fCameraHeight^2 -uniform float fOuterRadius; // The outer (atmosphere) radius -uniform float fOuterRadius2; // fOuterRadius^2 -uniform float fInnerRadius; // The inner (planetary) radius -uniform float fKrESun; // Kr * ESun -uniform float fKmESun; // Km * ESun -uniform float fKr4PI; // Kr * 4 * PI -uniform float fKm4PI; // Km * 4 * PI -uniform float fScale; // 1 / (fOuterRadius - fInnerRadius) -uniform float fScaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found) -uniform float fScaleOverScaleDepth; // fScale / fScaleDepth - -uniform float g; -uniform float g2; - -const int nSamples = 2; -const float fSamples = 2.0; - -in vec3 position; - -out vec4 outFragColor; - -float scale(float fCos) -{ - float x = 1.0 - fCos; - return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); -} - - -void main (void) -{ - // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) - vec3 v3Pos = position; - vec3 v3Ray = v3Pos - v3CameraPos; - float fFar = length(v3Ray); - v3Ray /= fFar; - - // Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray passing through the atmosphere) - float B = 2.0 * dot(v3CameraPos, v3Ray); - float C = fCameraHeight2 - fOuterRadius2; - float fDet = max(0.0, B*B - 4.0 * C); - float fNear = 0.5 * (-B - sqrt(fDet)); - - // Calculate the ray's starting position, then calculate its scattering offset - vec3 v3Start = v3CameraPos + v3Ray * fNear; - fFar -= fNear; - float fStartAngle = dot(v3Ray, v3Start) / fOuterRadius; - float fStartDepth = exp(-1.0 / fScaleDepth); - float fStartOffset = fStartDepth * scale(fStartAngle); - - // Initialize the scattering loop variables - //gl_FrontColor = vec4(0.0, 0.0, 0.0, 0.0); - float fSampleLength = fFar / fSamples; - float fScaledLength = fSampleLength * fScale; - vec3 v3SampleRay = v3Ray * fSampleLength; - vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; - - // Now loop through the sample rays - vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); - for(int i=0; i -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// For licensing information, see http://http.developer.nvidia.com/GPUGems/gpugems_app01.html: -// -// NVIDIA Statement on the Software -// -// The source code provided is freely distributable, so long as the NVIDIA header remains unaltered and user modifications are -// detailed. -// -// No Warranty -// -// THE SOFTWARE AND ANY OTHER MATERIALS PROVIDED BY NVIDIA ON THE ENCLOSED CD-ROM ARE PROVIDED "AS IS." NVIDIA DISCLAIMS ALL -// WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// -// Limitation of Liability -// -// NVIDIA SHALL NOT BE LIABLE TO ANY USER, DEVELOPER, DEVELOPER'S CUSTOMERS, OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH OR -// UNDER DEVELOPER FOR ANY LOSS OF PROFITS, INCOME, SAVINGS, OR ANY OTHER CONSEQUENTIAL, INCIDENTAL, SPECIAL, PUNITIVE, DIRECT -// OR INDIRECT DAMAGES (WHETHER IN AN ACTION IN CONTRACT, TORT OR BASED ON A WARRANTY), EVEN IF NVIDIA HAS BEEN ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF THE ESSENTIAL PURPOSE OF ANY -// LIMITED REMEDY. IN NO EVENT SHALL NVIDIA'S AGGREGATE LIABILITY TO DEVELOPER OR ANY OTHER PERSON OR ENTITY CLAIMING THROUGH -// OR UNDER DEVELOPER EXCEED THE AMOUNT OF MONEY ACTUALLY PAID BY DEVELOPER TO NVIDIA FOR THE SOFTWARE OR ANY OTHER MATERIALS. -// - -// -// Atmospheric scattering vertex shader -// -// Author: Sean O'Neil -// -// Copyright (c) 2004 Sean O'Neil -// - -<@include gpu/Inputs.slh@> -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> - -uniform float fOuterRadius; // The outer (atmosphere) radius - -out vec3 position; - - -void main(void) { - position = inPosition.xyz * fOuterRadius; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - vec4 v4pos = vec4(position, 1.0); - <$transformModelToClipPos(cam, obj, v4pos, gl_Position)$> -} diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 85c5810cd2..55b7d41ab3 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -18,6 +18,8 @@ #include "FramebufferCache.h" +const int ToneMappingEffect_ParamsSlot = 0; +const int ToneMappingEffect_LightingMapSlot = 0; ToneMappingEffect::ToneMappingEffect() { Parameters parameters; @@ -91,7 +93,8 @@ void ToneMappingEffect::init() { auto blitProgram = gpu::ShaderPointer(gpu::Shader::createProgram(blitVS, blitPS)); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("toneMappingParamsBuffer"), 3)); + slotBindings.insert(gpu::Shader::Binding(std::string("toneMappingParamsBuffer"), ToneMappingEffect_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), ToneMappingEffect_LightingMapSlot)); gpu::Shader::makeProgram(*blitProgram, slotBindings); auto blitState = std::make_shared(); blitState->setColorWriteMask(true, true, true, true); @@ -138,8 +141,8 @@ void ToneMappingEffect::render(RenderArgs* args) { batch.setModelTransform(model); } - batch.setUniformBuffer(3, _parametersBuffer); - batch.setResourceTexture(0, lightingBuffer); + batch.setUniformBuffer(ToneMappingEffect_ParamsSlot, _parametersBuffer); + batch.setResourceTexture(ToneMappingEffect_LightingMapSlot, lightingBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } \ No newline at end of file diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf deleted file mode 100644 index 8ab78891b0..0000000000 --- a/libraries/render-utils/src/ambient_occlusion.slf +++ /dev/null @@ -1,279 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// ambient_occlusion.frag -// fragment shader -// -// Created by Niraj Venkat on 7/15/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 -// - -<@include DeferredBufferWrite.slh@> - -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> - -// Based on NVidia HBAO implementation in D3D11 -// http://www.nvidia.co.uk/object/siggraph-2008-HBAO.html - -in vec2 varTexcoord; - -uniform sampler2D depthTexture; -uniform sampler2D normalTexture; - -uniform float g_scale; -uniform float g_bias; -uniform float g_sample_rad; -uniform float g_intensity; - -// the distance to the near clip plane -uniform float near; - -// scale factor for depth: (far - near) / far -uniform float depthScale; - -// offset for depth texture coordinates -uniform vec2 depthTexCoordOffset; - -// scale for depth texture coordinates -uniform vec2 depthTexCoordScale; - -// the resolution of the occlusion buffer -// and its inverse -uniform vec2 renderTargetRes; -uniform vec2 renderTargetResInv; - - - -const float PI = 3.14159265; - -const float AOStrength = 1.9; - - -// TODO: R (radius) should be exposed as a uniform parameter -const float R = 0.01; -const float R2 = 0.01*0.01; -const float NegInvR2 = - 1.0 / (0.01*0.01); - - - -// can't use tan to initialize a const value -const float TanBias = 0.57735027; // tan(30.0 * PI / 180.0); -const float MaxRadiusPixels = 50.0; - -const int NumDirections = 6; -const int NumSamples = 4; - -out vec4 outFragColor; - -/** - * Gets the normal in view space from a normal texture. - * uv: the uv texture coordinates to look up in the texture at. - */ -vec3 GetViewNormalFromTexture(vec2 uv) { - // convert [0,1] -> [-1,1], note: since we're normalizing - // we don't need to do v*2 - 1.0, we can just do a v-0.5 - return normalize(texture(normalTexture, uv).xyz - 0.5); -} - -/** - * Gets the linearized depth in view space. - * d: the depth value [0-1], usually from a depth texture to convert. - */ -float ViewSpaceZFromDepth(float d){ - return near / (d * depthScale - 1.0); -} - -/** - * Converts a uv coordinate and depth value into a 3D view space coordinate. - * uv: the uv coordinates to convert - * z: the view space depth of the uv coordinate. - */ -vec3 UVToViewSpace(vec2 uv, float z){ - return vec3((depthTexCoordOffset + varTexcoord * depthTexCoordScale) * z, z); -} - -/** - * Converts a uv coordinate into a 3D view space coordinate. - * The depth of the uv coord is determined from the depth texture. - * uv: the uv coordinates to convert - */ -vec3 GetViewPos(vec2 uv) { - float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); - return UVToViewSpace(uv, z); -} - - -float TanToSin(float x) { - return x * inversesqrt(x*x + 1.0); -} - -float InvLength(vec2 V) { - return inversesqrt(dot(V, V)); -} - -float Tangent(vec3 V) { - return V.z * InvLength(V.xy); -} - -float BiasedTangent(vec3 V) { - return V.z * InvLength(V.xy) + TanBias; -} - -float Tangent(vec3 P, vec3 S) { - return -(P.z - S.z) * InvLength(S.xy - P.xy); -} - -float Length2(vec3 V) { - return dot(V, V); -} - -vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl) { - vec3 V1 = Pr - P; - vec3 V2 = P - Pl; - return (Length2(V1) < Length2(V2)) ? V1 : V2; -} - -vec2 SnapUVOffset(vec2 uv) { - return round(uv * renderTargetRes) * renderTargetResInv; -} - -float Falloff(float d2) { - return d2 * NegInvR2 + 1.0f; -} - -float HorizonOcclusion(vec2 deltaUV, vec3 P, vec3 dPdu, vec3 dPdv, float randstep, float numSamples) { - float ao = 0; - - // Offset the first coord with some noise - vec2 uv = varTexcoord + SnapUVOffset(randstep*deltaUV); - deltaUV = SnapUVOffset(deltaUV); - - // Calculate the tangent vector - vec3 T = deltaUV.x * dPdu + deltaUV.y * dPdv; - - // Get the angle of the tangent vector from the viewspace axis - float tanH = BiasedTangent(T); - float sinH = TanToSin(tanH); - - float tanS; - float d2; - vec3 S; - - // Sample to find the maximum angle - for (float s = 1; s <= numSamples; ++s) { - uv += deltaUV; - S = GetViewPos(uv); - tanS = Tangent(P, S); - d2 = Length2(S - P); - - // Is the sample within the radius and the angle greater? - if (d2 < R2 && tanS > tanH) { - float sinS = TanToSin(tanS); - // Apply falloff based on the distance - ao += Falloff(d2) * (sinS - sinH); - - tanH = tanS; - sinH = sinS; - } - } - return ao; -} - -vec2 RotateDirections(vec2 Dir, vec2 CosSin) { - return vec2(Dir.x*CosSin.x - Dir.y*CosSin.y, - Dir.x*CosSin.y + Dir.y*CosSin.x); -} - -void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPix, float rand) { - // Avoid oversampling if numSteps is greater than the kernel radius in pixels - numSteps = min(NumSamples, rayRadiusPix); - - // Divide by Ns+1 so that the farthest samples are not fully attenuated - float stepSizePix = rayRadiusPix / (numSteps + 1); - - // Clamp numSteps if it is greater than the max kernel footprint - float maxNumSteps = MaxRadiusPixels / stepSizePix; - if (maxNumSteps < numSteps) { - // Use dithering to avoid AO discontinuities - numSteps = floor(maxNumSteps + rand); - numSteps = max(numSteps, 1); - stepSizePix = MaxRadiusPixels / numSteps; - } - - // Step size in uv space - stepSizeUv = stepSizePix * renderTargetResInv; -} - -float getRandom(vec2 uv) { - return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453); -} - -void main(void) { - mat4 projMatrix = getTransformCamera()._projection; - - float numDirections = NumDirections; - - vec3 P, Pr, Pl, Pt, Pb; - P = GetViewPos(varTexcoord); - - // Sample neighboring pixels - Pr = GetViewPos(varTexcoord + vec2( renderTargetResInv.x, 0)); - Pl = GetViewPos(varTexcoord + vec2(-renderTargetResInv.x, 0)); - Pt = GetViewPos(varTexcoord + vec2( 0, renderTargetResInv.y)); - Pb = GetViewPos(varTexcoord + vec2( 0,-renderTargetResInv.y)); - - // Calculate tangent basis vectors using the minimum difference - vec3 dPdu = MinDiff(P, Pr, Pl); - vec3 dPdv = MinDiff(P, Pt, Pb) * (renderTargetRes.y * renderTargetResInv.x); - - // Get the random samples from the noise function - vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); - - // Calculate the projected size of the hemisphere - float w = P.z * projMatrix[2][3] + projMatrix[3][3]; - vec2 rayRadiusUV = (0.5 * R * vec2(projMatrix[0][0], projMatrix[1][1]) / w); // [-1,1] -> [0,1] uv - float rayRadiusPix = rayRadiusUV.x * renderTargetRes.x; - - float ao = 1.0; - - // Make sure the radius of the evaluated hemisphere is more than a pixel - if(rayRadiusPix > 1.0) { - ao = 0.0; - float numSteps; - vec2 stepSizeUV; - - // Compute the number of steps - ComputeSteps(stepSizeUV, numSteps, rayRadiusPix, random.z); - - float alpha = 2.0 * PI / numDirections; - - // Calculate the horizon occlusion of each direction - for(float d = 0; d < numDirections; ++d) { - float theta = alpha * d; - - // Apply noise to the direction - vec2 dir = RotateDirections(vec2(cos(theta), sin(theta)), random.xy); - vec2 deltaUV = dir * stepSizeUV; - - // Sample the pixels along the direction - ao += HorizonOcclusion( deltaUV, - P, - dPdu, - dPdv, - random.z, - numSteps); - } - - // Average the results and produce the final AO - ao = 1.0 - ao / numDirections * AOStrength; - } - - - outFragColor = vec4(vec3(ao), 1.0); -} diff --git a/libraries/render-utils/src/ambient_occlusion.slv b/libraries/render-utils/src/ambient_occlusion.slv deleted file mode 100644 index 46da3f5fd5..0000000000 --- a/libraries/render-utils/src/ambient_occlusion.slv +++ /dev/null @@ -1,26 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// ambient_occlusion.vert -// vertex shader -// -// Created by Niraj Venkat on 7/15/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 -// - -<@include gpu/Inputs.slh@> - -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> - -out vec2 varTexcoord; - -void main(void) { - varTexcoord = inTexCoord0.xy; - gl_Position = inPosition; -} diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index 375972cdc3..bbc50fa97a 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -14,6 +14,10 @@ <@include DeferredBuffer.slh@> +uniform sampler2D pyramidMap; +uniform sampler2D occlusionMap; +uniform sampler2D occlusionBlurredMap; + in vec2 uv; out vec4 outFragColor; diff --git a/libraries/render-utils/src/directional_ambient_light.slf b/libraries/render-utils/src/directional_ambient_light.slf index 5c0a5c8331..3d1b9db46c 100755 --- a/libraries/render-utils/src/directional_ambient_light.slf +++ b/libraries/render-utils/src/directional_ambient_light.slf @@ -25,12 +25,13 @@ void main(void) { DeferredTransform deferredTransform = getDeferredTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - float shadowAttenuation = 1.0; + float shadowAttenuation = 1.0; - if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + if (frag.mode == LIGHT_MAPPED) { vec3 color = evalLightmappedColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.normal, frag.diffuse, frag.specularVal.xyz); @@ -39,6 +40,7 @@ void main(void) { vec3 color = evalAmbientSphereGlobalColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.position.xyz, frag.normal, frag.diffuse, diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index 1cb425c881..ffe0e21851 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -27,12 +27,13 @@ void main(void) { DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); vec4 worldPos = deferredTransform.viewInverse * vec4(frag.position.xyz, 1.0); - float shadowAttenuation = evalShadowAttenuation(worldPos); + float shadowAttenuation = evalShadowAttenuation(worldPos); - if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + if (frag.mode == LIGHT_MAPPED) { vec3 color = evalLightmappedColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.normal, frag.diffuse, frag.specularVal.xyz); @@ -41,6 +42,7 @@ void main(void) { vec3 color = evalAmbientSphereGlobalColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.position.xyz, frag.normal, frag.diffuse, diff --git a/libraries/render-utils/src/directional_light.slf b/libraries/render-utils/src/directional_light.slf index 9318f07f5a..e07c57a905 100644 --- a/libraries/render-utils/src/directional_light.slf +++ b/libraries/render-utils/src/directional_light.slf @@ -25,13 +25,14 @@ void main(void) { DeferredTransform deferredTransform = getDeferredTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - float shadowAttenuation = 1.0; + float shadowAttenuation = 1.0; // Light mapped or not ? - if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + if (frag.mode == LIGHT_MAPPED) { vec3 color = evalLightmappedColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.normal, frag.diffuse, frag.specularVal.xyz); @@ -39,7 +40,8 @@ void main(void) { } else { vec3 color = evalAmbientGlobalColor( deferredTransform.viewInverse, - shadowAttenuation, + shadowAttenuation, + frag.obscurance, frag.position.xyz, frag.normal, frag.diffuse, diff --git a/libraries/render-utils/src/directional_light_shadow.slf b/libraries/render-utils/src/directional_light_shadow.slf index 6c7c450bc5..4aa041b847 100644 --- a/libraries/render-utils/src/directional_light_shadow.slf +++ b/libraries/render-utils/src/directional_light_shadow.slf @@ -27,13 +27,14 @@ void main(void) { DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); vec4 worldPos = deferredTransform.viewInverse * vec4(frag.position.xyz, 1.0); - float shadowAttenuation = evalShadowAttenuation(worldPos); + float shadowAttenuation = evalShadowAttenuation(worldPos); // Light mapped or not ? - if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + if (frag.mode == LIGHT_MAPPED) { vec3 color = evalLightmappedColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.normal, frag.diffuse, frag.specularVal.xyz); @@ -41,7 +42,8 @@ void main(void) { } else { vec3 color = evalAmbientGlobalColor( deferredTransform.viewInverse, - shadowAttenuation, + shadowAttenuation, + frag.obscurance, frag.position.xyz, frag.normal, frag.diffuse, diff --git a/libraries/render-utils/src/directional_skybox_light.slf b/libraries/render-utils/src/directional_skybox_light.slf index d149a78a14..78fdc4e234 100755 --- a/libraries/render-utils/src/directional_skybox_light.slf +++ b/libraries/render-utils/src/directional_skybox_light.slf @@ -25,13 +25,14 @@ void main(void) { DeferredTransform deferredTransform = getDeferredTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - float shadowAttenuation = 1.0; + float shadowAttenuation = 1.0; // Light mapped or not ? - if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + if (frag.mode == LIGHT_MAPPED) { vec3 color = evalLightmappedColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.normal, frag.diffuse, frag.specularVal.xyz); @@ -40,6 +41,7 @@ void main(void) { vec3 color = evalSkyboxGlobalColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.position.xyz, frag.normal, frag.diffuse, diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index 1b7ef76035..bbce15be68 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -27,13 +27,14 @@ void main(void) { DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); vec4 worldPos = deferredTransform.viewInverse * vec4(frag.position.xyz, 1.0); - float shadowAttenuation = evalShadowAttenuation(worldPos); + float shadowAttenuation = evalShadowAttenuation(worldPos); // Light mapped or not ? - if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + if (frag.mode == LIGHT_MAPPED) { vec3 color = evalLightmappedColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.normal, frag.diffuse, frag.specularVal.xyz); @@ -42,6 +43,7 @@ void main(void) { vec3 color = evalSkyboxGlobalColor( deferredTransform.viewInverse, shadowAttenuation, + frag.obscurance, frag.position.xyz, frag.normal, frag.diffuse, diff --git a/libraries/render-utils/src/gaussian_blur.slf b/libraries/render-utils/src/gaussian_blur.slf deleted file mode 100644 index 01c1bc8807..0000000000 --- a/libraries/render-utils/src/gaussian_blur.slf +++ /dev/null @@ -1,43 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// gaussian_blur.frag -// fragment shader -// -// Created by Niraj Venkat on 7/17/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 -// - -<@include DeferredBufferWrite.slh@> - -// the interpolated normal -//in vec4 interpolatedNormal; - -in vec2 varTexcoord; -in vec2 varBlurTexcoords[14]; - -uniform sampler2D occlusionTexture; - -out vec4 outFragColor; -void main(void) { - outFragColor = vec4(0.0); - outFragColor += texture(occlusionTexture, varBlurTexcoords[0])*0.0044299121055113265; - outFragColor += texture(occlusionTexture, varBlurTexcoords[1])*0.00895781211794; - outFragColor += texture(occlusionTexture, varBlurTexcoords[2])*0.0215963866053; - outFragColor += texture(occlusionTexture, varBlurTexcoords[3])*0.0443683338718; - outFragColor += texture(occlusionTexture, varBlurTexcoords[4])*0.0776744219933; - outFragColor += texture(occlusionTexture, varBlurTexcoords[5])*0.115876621105; - outFragColor += texture(occlusionTexture, varBlurTexcoords[6])*0.147308056121; - outFragColor += texture(occlusionTexture, varTexcoord)*0.159576912161; - outFragColor += texture(occlusionTexture, varBlurTexcoords[7])*0.147308056121; - outFragColor += texture(occlusionTexture, varBlurTexcoords[8])*0.115876621105; - outFragColor += texture(occlusionTexture, varBlurTexcoords[9])*0.0776744219933; - outFragColor += texture(occlusionTexture, varBlurTexcoords[10])*0.0443683338718; - outFragColor += texture(occlusionTexture, varBlurTexcoords[11])*0.0215963866053; - outFragColor += texture(occlusionTexture, varBlurTexcoords[12])*0.00895781211794; - outFragColor += texture(occlusionTexture, varBlurTexcoords[13])*0.0044299121055113265; -} diff --git a/libraries/render-utils/src/gaussian_blur_horizontal.slv b/libraries/render-utils/src/gaussian_blur_horizontal.slv deleted file mode 100644 index 7f8fb1c87f..0000000000 --- a/libraries/render-utils/src/gaussian_blur_horizontal.slv +++ /dev/null @@ -1,43 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// guassian_blur_horizontal.vert -// vertex shader -// -// Created by Niraj Venkat on 7/17/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 -// - -<@include gpu/Inputs.slh@> - -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> - -out vec2 varTexcoord; -out vec2 varBlurTexcoords[14]; - -void main(void) { - varTexcoord = inTexCoord0.xy; - gl_Position = inPosition; - - varBlurTexcoords[0] = varTexcoord + vec2(-0.028, 0.0); - varBlurTexcoords[1] = varTexcoord + vec2(-0.024, 0.0); - varBlurTexcoords[2] = varTexcoord + vec2(-0.020, 0.0); - varBlurTexcoords[3] = varTexcoord + vec2(-0.016, 0.0); - varBlurTexcoords[4] = varTexcoord + vec2(-0.012, 0.0); - varBlurTexcoords[5] = varTexcoord + vec2(-0.008, 0.0); - varBlurTexcoords[6] = varTexcoord + vec2(-0.004, 0.0); - varBlurTexcoords[7] = varTexcoord + vec2(0.004, 0.0); - varBlurTexcoords[8] = varTexcoord + vec2(0.008, 0.0); - varBlurTexcoords[9] = varTexcoord + vec2(0.012, 0.0); - varBlurTexcoords[10] = varTexcoord + vec2(0.016, 0.0); - varBlurTexcoords[11] = varTexcoord + vec2(0.020, 0.0); - varBlurTexcoords[12] = varTexcoord + vec2(0.024, 0.0); - varBlurTexcoords[13] = varTexcoord + vec2(0.028, 0.0); -} - \ No newline at end of file diff --git a/libraries/render-utils/src/gaussian_blur_vertical.slv b/libraries/render-utils/src/gaussian_blur_vertical.slv deleted file mode 100644 index 4a4d65dcf9..0000000000 --- a/libraries/render-utils/src/gaussian_blur_vertical.slv +++ /dev/null @@ -1,43 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// guassian_blur_vertical.vert -// vertex shader -// -// Created by Niraj Venkat on 7/17/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 -// - -<@include gpu/Inputs.slh@> - -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> - -out vec2 varTexcoord; -out vec2 varBlurTexcoords[14]; - -void main(void) { - varTexcoord = inTexCoord0.xy; - gl_Position = inPosition; - - varBlurTexcoords[0] = varTexcoord + vec2(0.0, -0.028); - varBlurTexcoords[1] = varTexcoord + vec2(0.0, -0.024); - varBlurTexcoords[2] = varTexcoord + vec2(0.0, -0.020); - varBlurTexcoords[3] = varTexcoord + vec2(0.0, -0.016); - varBlurTexcoords[4] = varTexcoord + vec2(0.0, -0.012); - varBlurTexcoords[5] = varTexcoord + vec2(0.0, -0.008); - varBlurTexcoords[6] = varTexcoord + vec2(0.0, -0.004); - varBlurTexcoords[7] = varTexcoord + vec2(0.0, 0.004); - varBlurTexcoords[8] = varTexcoord + vec2(0.0, 0.008); - varBlurTexcoords[9] = varTexcoord + vec2(0.0, 0.012); - varBlurTexcoords[10] = varTexcoord + vec2(0.0, 0.016); - varBlurTexcoords[11] = varTexcoord + vec2(0.0, 0.020); - varBlurTexcoords[12] = varTexcoord + vec2(0.0, 0.024); - varBlurTexcoords[13] = varTexcoord + vec2(0.0, 0.028); -} - \ No newline at end of file diff --git a/libraries/render-utils/src/occlusion_blend.slf b/libraries/render-utils/src/occlusion_blend.slf deleted file mode 100644 index 58873d9884..0000000000 --- a/libraries/render-utils/src/occlusion_blend.slf +++ /dev/null @@ -1,27 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// occlusion_blend.frag -// fragment shader -// -// Created by Niraj Venkat on 7/20/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 -// - -<@include DeferredBufferWrite.slh@> - -in vec2 varTexcoord; -out vec4 outFragColor; - -uniform sampler2D blurredOcclusionTexture; - -void main(void) { - vec4 occlusionColor = texture(blurredOcclusionTexture, varTexcoord); - - outFragColor = vec4(vec3(0.0), occlusionColor.r); - -} diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index 716a39aee9..fcfb0b336e 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -67,7 +67,7 @@ void main(void) { // Final Lighting color vec3 fragColor = (shading.w * frag.diffuse + shading.xyz); - _fragColor = vec4(fragColor * radialAttenuation * getLightColor(light) * getLightIntensity(light), 0.0); + _fragColor = vec4(fragColor * radialAttenuation * getLightColor(light) * getLightIntensity(light) * frag.obscurance, 0.0); if (getLightShowContour(light) > 0.0) { // Show edge diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index d7a20fc5e5..8170929636 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -74,7 +74,7 @@ void main(void) { // Final Lighting color vec3 fragColor = (shading.w * frag.diffuse + shading.xyz); - _fragColor = vec4(fragColor * angularAttenuation * radialAttenuation * getLightColor(light) * getLightIntensity(light), 0.0); + _fragColor = vec4(fragColor * angularAttenuation * radialAttenuation * getLightColor(light) * getLightIntensity(light) * frag.obscurance, 0.0); if (getLightShowContour(light) > 0.0) { // Show edges diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh new file mode 100644 index 0000000000..f3c52bc1a6 --- /dev/null +++ b/libraries/render-utils/src/ssao.slh @@ -0,0 +1,243 @@ + +<@if not SSAO_SLH@> +<@def SSAO_SLH@> + +<@func declarePackOcclusionDepth()@> + +const float FAR_PLANE_Z = -300.0; + +float CSZToDephtKey(float z) { + return clamp(z * (1.0 / FAR_PLANE_Z), 0.0, 1.0); +} +vec3 packOcclusionDepth(float occlusion, float depth) { + // Round to the nearest 1/256.0 + float temp = floor(depth * 256.0); + return vec3(occlusion, temp * (1.0 / 256.0), depth * 256.0 - temp); +} +vec2 unpackOcclusionDepth(vec3 raw) { + float z = raw.y * (256.0 / 257.0) + raw.z * (1.0 / 257.0); + return vec2(raw.x, z); +} +<@endfunc@> + +<@func declareAmbientOcclusion()@> + +struct AmbientOcclusionFrameTransform { + vec4 _pixelInfo; + vec4 _depthInfo; + vec4 _stereoInfo; + mat4 _projection[2]; +}; + +struct AmbientOcclusionParams { + vec4 _resolutionInfo; + vec4 _radiusInfo; + vec4 _ditheringInfo; + vec4 _sampleInfo; + vec4 _blurInfo; + float _gaussianCoefs[8]; +}; + +uniform ambientOcclusionFrameTransformBuffer { + AmbientOcclusionFrameTransform frameTransform; +}; +uniform ambientOcclusionParamsBuffer { + AmbientOcclusionParams params; +}; + + +int getResolutionLevel() { + return int(params._resolutionInfo.x); +} + +vec2 getWidthHeight() { + return vec2(ivec2(frameTransform._pixelInfo.zw) >> getResolutionLevel()); +} +float getProjScale() { + return getWidthHeight().y * frameTransform._projection[0][1][1] * 0.5; +} +mat4 getProjection(int side) { + return frameTransform._projection[side]; +} + +bool isStereo() { + return frameTransform._stereoInfo.x > 0.0f; +} + +float getStereoSideWidth() { + return float(int(frameTransform._stereoInfo.y) >> getResolutionLevel()); +} + +ivec3 getStereoSideInfo(int xPos) { + int sideWidth = int(getStereoSideWidth()); + return ivec3(xPos < sideWidth ? ivec2(0, 0) : ivec2(1, sideWidth), sideWidth); +} + + +float evalZeyeFromZdb(float depth) { + return frameTransform._depthInfo.x / (depth * frameTransform._depthInfo.y + frameTransform._depthInfo.z); +} + +vec3 evalEyeNormal(vec3 C) { + //return normalize(cross(dFdy(C), dFdx(C))); + return normalize(cross(dFdx(C), dFdy(C))); +} + + +float getRadius() { + return params._radiusInfo.x; +} +float getRadius2() { + return params._radiusInfo.y; +} +float getInvRadius6() { + return params._radiusInfo.z; +} +float getObscuranceScaling() { + return params._radiusInfo.z * params._radiusInfo.w; +} + +float isDitheringEnabled() { + return params._ditheringInfo.x; +} +float getFrameDithering() { + return params._ditheringInfo.y; +} +float isBorderingEnabled() { + return params._ditheringInfo.w; +} + +float getFalloffBias() { + return params._ditheringInfo.z; +} + +float getNumSamples() { + return params._sampleInfo.x; +} +float getInvNumSamples() { + return params._sampleInfo.y; +} +float getNumSpiralTurns() { + return params._sampleInfo.z; +} + +float getBlurEdgeSharpness() { + return params._blurInfo.x; +} + +#ifdef CONSTANT_GAUSSIAN +const int BLUR_RADIUS = 4; +const float gaussian[BLUR_RADIUS + 1] = +// KEEP this dead code for eventual performance improvment +// float[](0.356642, 0.239400, 0.072410, 0.009869); +// float[](0.398943, 0.241971, 0.053991, 0.004432, 0.000134); // stddev = 1.0 +float[](0.153170, 0.144893, 0.122649, 0.092902, 0.062970); // stddev = 2.0 +//float[](0.197413, 0.17467, 0.12098,0.065591,0.040059); +// float[](0.111220, 0.107798, 0.098151, 0.083953, 0.067458, 0.050920, 0.036108); // stddev = 3.0 + +int getBlurRadius() { + return BLUR_RADIUS; + return int(params._blurInfo.y); +} + +float getBlurCoef(int c) { + return gaussian[c]; + return params._gaussianCoefs[c]; +} +#else +int getBlurRadius() { + return int(params._blurInfo.y); +} + +float getBlurCoef(int c) { + return params._gaussianCoefs[c]; +} +#endif + +<@endfunc@> + +<@func declareBlurPass(axis)@> + +<$declarePackOcclusionDepth()$> +<$declareAmbientOcclusion()$> + +// the source occlusion texture +uniform sampler2D occlusionMap; + +vec2 fetchOcclusionDepthRaw(ivec2 coords, out vec3 raw) { + raw = texelFetch(occlusionMap, coords, 0).xyz; + return unpackOcclusionDepth(raw); +} + +vec2 fetchOcclusionDepth(ivec2 coords) { + return unpackOcclusionDepth(texelFetch(occlusionMap, coords, 0).xyz); +} + +const int RADIUS_SCALE = 2; +const float BLUR_WEIGHT_OFFSET = 0.3; +const float BLUR_EDGE_SCALE = 2000.0; + +vec2 evalTapWeightedValue(ivec3 side, int r, ivec2 ssC, float key) { + ivec2 tapOffset = <$axis$> * (r * RADIUS_SCALE); + ivec2 ssP = (ssC + tapOffset); + + if ((ssP.x < side.y || ssP.x >= side.z + side.y) || (ssP.y < 0 || ssP.y >= int(getWidthHeight().y))) { + return vec2(0.0); + } + vec2 tapOZ = fetchOcclusionDepth(ssC + tapOffset); + + // spatial domain: offset gaussian tap + float weight = BLUR_WEIGHT_OFFSET + getBlurCoef(abs(r)); + + // range domain (the "bilateral" weight). As depth difference increases, decrease weight. + weight *= max(0.0, 1.0 - (getBlurEdgeSharpness() * BLUR_EDGE_SCALE) * abs(tapOZ.y - key)); + + return vec2(tapOZ.x * weight, weight); +} + +vec3 getBlurredOcclusion(vec2 coord) { + ivec2 ssC = ivec2(coord); + + // Stereo side info + ivec3 side = getStereoSideInfo(ssC.x); + + vec3 rawSample; + vec2 occlusionDepth = fetchOcclusionDepthRaw(ssC, rawSample); + float key = occlusionDepth.y; + + // Central pixel contribution + float mainWeight = getBlurCoef(0); + vec2 weightedSums = vec2(occlusionDepth.x * mainWeight, mainWeight); + + // Accumulate weighted contributions along the bluring axis in the [-radius, radius] range + int blurRadius = getBlurRadius(); + // negative side first + for (int r = -blurRadius; r <= -1; ++r) { + weightedSums += evalTapWeightedValue(side, r, ssC, key); + } + // then positive side + for (int r = 1; r <= blurRadius; ++r) { + weightedSums += evalTapWeightedValue(side, r, ssC, key); + } + + // Final normalization + const float epsilon = 0.0001; + float result = weightedSums.x / (weightedSums.y + epsilon); + + rawSample.x = result; + return rawSample; +} + +<@endfunc@> + + +<@endif@> \ No newline at end of file diff --git a/libraries/render-utils/src/ssao_makeHorizontalBlur.slf b/libraries/render-utils/src/ssao_makeHorizontalBlur.slf new file mode 100644 index 0000000000..7be4f527ce --- /dev/null +++ b/libraries/render-utils/src/ssao_makeHorizontalBlur.slf @@ -0,0 +1,23 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 1/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include ssao.slh@> + +const ivec2 horizontal = ivec2(1,0); +<$declareBlurPass(horizontal)$> + + +in vec2 varTexCoord0; +out vec4 outFragColor; + +void main(void) { + outFragColor = vec4(getBlurredOcclusion(gl_FragCoord.xy), 1.0); +} diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf new file mode 100644 index 0000000000..72424cad1e --- /dev/null +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -0,0 +1,161 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 1/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include ssao.slh@> +<$declareAmbientOcclusion()$> +<$declarePackOcclusionDepth()$> + + +const int LOG_MAX_OFFSET = 3; +const int MAX_MIP_LEVEL = 5; + +// the depth pyramid texture +uniform sampler2D pyramidMap; + +float getZEye(ivec2 pixel) { + return -texelFetch(pyramidMap, pixel, getResolutionLevel()).x; +} + +vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { + // compute the view space position using the depth + // basically manually pick the proj matrix components to do the inverse + float Xe = (-Zeye * (texcoord.x * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][0] - frameTransform._projection[side][3][0]) / frameTransform._projection[side][0][0]; + float Ye = (-Zeye * (texcoord.y * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][1] - frameTransform._projection[side][3][1]) / frameTransform._projection[side][1][1]; + return vec3(Xe, Ye, Zeye); +} + +out vec4 outFragColor; + +uniform sampler2D normalMap; + +float getAngleDithering(in ivec2 pixelPos) { + // Hash function used in the AlchemyAO paper + return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); +} + +const float TWO_PI = 6.28; + +vec2 tapLocation(int sampleNumber, float spinAngle, out float ssR){ + // Radius relative to ssR + float alpha = float(sampleNumber + 0.5) * getInvNumSamples(); + float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle; + + ssR = alpha; + return vec2(cos(angle), sin(angle)); +} + +vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec2 unitOffset, float ssR) { + // Derivation: + // mipLevel = floor(log(ssR / MAX_OFFSET)); + int mipLevel = clamp(findMSB(int(ssR)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL); + + ivec2 ssOffset = ivec2(ssR * unitOffset); + ivec2 ssP = ssOffset + ssC; + if (bool(isBorderingEnabled())) { + ssP.x = ((ssP.x < 0 || ssP.x >= side.z) ? ssC.x - ssOffset.x : ssP.x); + ssP.y = ((ssP.y < 0 || ssP.y >= int(getWidthHeight().y)) ? ssC.y - ssOffset.y : ssP.y); + } + + ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y); + + vec3 P; + + // We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map. + // Manually clamp to the texture size because texelFetch bypasses the texture unit + ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), textureSize(pyramidMap, getResolutionLevel() + mipLevel) - ivec2(1)); + P.z = -texelFetch(pyramidMap, mipP, getResolutionLevel() + mipLevel).r; + + // Offset to pixel center + vec2 tapUV = (vec2(ssP) + vec2(0.5)) / float(side.z); + P = evalEyePositionFromZeye(side.x, P.z, tapUV); + return P; +} + +float sampleAO(in ivec3 side, in ivec2 ssC, in vec3 C, in vec3 n_C, in float ssDiskRadius, in int tapIndex, in float randomPatternRotationAngle) { + // Offset on the unit disk, spun for this pixel + float ssR; + vec2 unitOffset = tapLocation(tapIndex, randomPatternRotationAngle, ssR); + ssR *= ssDiskRadius; + + + + // The occluding point in camera space + vec3 Q = getOffsetPosition(side, ssC, unitOffset, ssR); + + vec3 v = Q - C; + float vv = dot(v, v); + float vn = dot(v, n_C); + + // Fall off function as recommended in SAO paper + const float epsilon = 0.01; + float f = max(getRadius2() - vv, 0.0); + return f * f * f * max((vn - getFalloffBias()) / (epsilon + vv), 0.0); +} + +void main(void) { + // Pixel being shaded + ivec2 ssC = ivec2(gl_FragCoord.xy); + + // Fetch the z under the pixel (stereo or not) + float Zeye = getZEye(ssC); + + // Stereo side info + ivec3 side = getStereoSideInfo(ssC.x); + + // From now on, ssC is the pixel pos in the side + ssC.x -= side.y; + vec2 fragPos = (vec2(ssC) + 0.5) / getStereoSideWidth(); + + // The position and normal of the pixel fragment in Eye space + vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos); + vec3 Cn = evalEyeNormal(Cp); + + // Choose the screen-space sample radius + // proportional to the projected area of the sphere + float ssDiskRadius = -getProjScale() * getRadius() / Cp.z; + + // Let's make noise + float randomPatternRotationAngle = getAngleDithering(ssC); + + // Accumulate the Obscurance for each samples + float sum = 0.0; + for (int i = 0; i < getNumSamples(); ++i) { + sum += sampleAO(side, ssC, Cp, Cn, ssDiskRadius, i, randomPatternRotationAngle); + } + + float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples()); + + + + outFragColor = vec4(packOcclusionDepth(A, CSZToDephtKey(Cp.z)), 1.0); + + +} diff --git a/libraries/render-utils/src/ssao_makePyramid.slf b/libraries/render-utils/src/ssao_makePyramid.slf new file mode 100644 index 0000000000..70d46fb432 --- /dev/null +++ b/libraries/render-utils/src/ssao_makePyramid.slf @@ -0,0 +1,23 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 1/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include ssao.slh@> +<$declareAmbientOcclusion()$> + +uniform sampler2D depthMap; + +out vec4 outFragColor; + +void main(void) { + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + float Zeye = -evalZeyeFromZdb(Zdb); + outFragColor = vec4(Zeye, 0.0, 0.0, 1.0); +} diff --git a/libraries/render-utils/src/ssao_makeVerticalBlur.slf b/libraries/render-utils/src/ssao_makeVerticalBlur.slf new file mode 100644 index 0000000000..f0a6139906 --- /dev/null +++ b/libraries/render-utils/src/ssao_makeVerticalBlur.slf @@ -0,0 +1,22 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 1/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +<@include ssao.slh@> + +const ivec2 vertical = ivec2(0,1); +<$declareBlurPass(vertical)$> + +in vec2 varTexCoord0; +out vec4 outFragColor; + +void main(void) { + float occlusion = getBlurredOcclusion(gl_FragCoord.xy).x; + outFragColor = vec4(occlusion, 0.0, 0.0, occlusion); +} diff --git a/libraries/render/src/render/Context.cpp b/libraries/render/src/render/Context.cpp index 0e07726eb4..0a392e366e 100644 --- a/libraries/render/src/render/Context.cpp +++ b/libraries/render/src/render/Context.cpp @@ -13,13 +13,11 @@ using namespace render; -RenderContext::RenderContext(ItemsConfig items, Tone tone, int drawStatus, bool drawHitEffect, glm::vec4 deferredDebugSize, int deferredDebugMode) +RenderContext::RenderContext(ItemsConfig items, Tone tone, AmbientOcclusion ao, int drawStatus, bool drawHitEffect, glm::vec4 deferredDebugSize, int deferredDebugMode) : _deferredDebugMode{ deferredDebugMode }, _deferredDebugSize{ deferredDebugSize }, _args{ nullptr }, _drawStatus{ drawStatus }, _drawHitEffect{ drawHitEffect }, - _items{ items }, _tone{ tone } -{ -} + _items{ items }, _tone{ tone }, _ambientOcclusion{ ao } {} void RenderContext::setOptions(bool occlusion, bool fxaa, bool showOwned, bool shadowMap) { _occlusionStatus = occlusion; diff --git a/libraries/render/src/render/Context.h b/libraries/render/src/render/Context.h index c1d26d0ccb..33c4c13a4c 100644 --- a/libraries/render/src/render/Context.h +++ b/libraries/render/src/render/Context.h @@ -72,13 +72,30 @@ public: float exposure = 0.0; }; - RenderContext(ItemsConfig items, Tone tone, int drawStatus, bool drawHitEffect, glm::vec4 deferredDebugSize, int deferredDebugMode); - RenderContext() {} + class AmbientOcclusion { + public: + int resolutionLevel { 1 }; + float radius { 0.5f }; // radius in meters of the AO effect + float level { 0.5f }; // Level of the obscrance value + int numSamples { 11 }; // Num Samples per pixel + float numSpiralTurns { 7.0f }; + bool ditheringEnabled { true }; + float falloffBias { 0.01f }; + float edgeSharpness { 1.0f }; + int blurRadius { 4 }; + float blurDeviation { 2.5f}; + + double gpuTime { 0.0 }; + }; + + RenderContext(ItemsConfig items, Tone tone, AmbientOcclusion ao, int drawStatus, bool drawHitEffect, glm::vec4 deferredDebugSize, int deferredDebugMode); + RenderContext() {}; void setArgs(RenderArgs* args) { _args = args; } RenderArgs* getArgs() { return _args; } ItemsConfig& getItemsConfig() { return _items; } Tone& getTone() { return _tone; } + AmbientOcclusion& getAmbientOcclusion() { return _ambientOcclusion; } int getDrawStatus() { return _drawStatus; } bool getDrawHitEffect() { return _drawHitEffect; } bool getOcclusionStatus() { return _occlusionStatus; } @@ -97,11 +114,12 @@ protected: int _drawStatus; // bitflag bool _drawHitEffect; bool _occlusionStatus { false }; - bool _fxaaStatus = { false }; - bool _shadowMapStatus = { false }; + bool _fxaaStatus { false }; + bool _shadowMapStatus { false }; ItemsConfig _items; Tone _tone; + AmbientOcclusion _ambientOcclusion; }; typedef std::shared_ptr RenderContextPointer; diff --git a/libraries/script-engine/src/SceneScriptingInterface.cpp b/libraries/script-engine/src/SceneScriptingInterface.cpp index 062e502d6e..b2f3824221 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.cpp +++ b/libraries/script-engine/src/SceneScriptingInterface.cpp @@ -104,8 +104,6 @@ bool SceneScripting::Stage::isSunModelEnabled() const { void SceneScripting::Stage::setBackgroundMode(const QString& mode) { if (mode == QString("inherit")) { _skyStage->setBackgroundMode(model::SunSkyStage::NO_BACKGROUND); - } else if (mode == QString("atmosphere")) { - _skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); } else if (mode == QString("skybox")) { _skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX); } @@ -115,8 +113,6 @@ QString SceneScripting::Stage::getBackgroundMode() const { switch (_skyStage->getBackgroundMode()) { case model::SunSkyStage::NO_BACKGROUND: return QString("inherit"); - case model::SunSkyStage::SKY_DOME: - return QString("atmosphere"); case model::SunSkyStage::SKY_BOX: return QString("skybox"); default: diff --git a/libraries/shared/src/BackgroundMode.h b/libraries/shared/src/BackgroundMode.h index 661fe0f849..e6e585d9d8 100644 --- a/libraries/shared/src/BackgroundMode.h +++ b/libraries/shared/src/BackgroundMode.h @@ -13,8 +13,9 @@ enum BackgroundMode { BACKGROUND_MODE_INHERIT, - BACKGROUND_MODE_ATMOSPHERE, BACKGROUND_MODE_SKYBOX, + + BACKGROUND_MODE_ITEM_COUNT, }; diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 4f9a741aad..b3920e70bc 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -9,8 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "HifiConfigVariantMap.h" + #include #include +#include #include #include #include @@ -18,8 +21,8 @@ #include #include +#include "ServerPathUtils.h" #include "SharedLogging.h" -#include "HifiConfigVariantMap.h" QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringList& argumentList) { @@ -110,18 +113,61 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi // load the user config const QString USER_CONFIG_FILE_OPTION = "--user-config"; + static const QString USER_CONFIG_FILE_NAME = "config.json"; int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION); if (userConfigIndex != -1) { _userConfigFilename = argumentList[userConfigIndex + 1]; } else { - _userConfigFilename = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), - QCoreApplication::organizationName(), - QCoreApplication::applicationName()); + // we weren't passed a user config path + _userConfigFilename = ServerPathUtils::getDataFilePath(USER_CONFIG_FILE_NAME); + + // as of 1/19/2016 this path was moved so we attempt a migration for first run post migration here + + // figure out what the old path was + + // if our build version is "dev" we should migrate from a different organization folder + + auto oldConfigFilename = QString("%1/%2/%3/%4").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), + QCoreApplication::organizationName(), + QCoreApplication::applicationName(), + USER_CONFIG_FILE_NAME); + + oldConfigFilename = oldConfigFilename.replace("High Fidelity - dev", "High Fidelity"); + + + // check if there's already a config file at the new path + QFile newConfigFile { _userConfigFilename }; + if (!newConfigFile.exists()) { + + QFile oldConfigFile { oldConfigFilename }; + + if (oldConfigFile.exists()) { + // we have the old file and not the new file - time to copy the file + + // make the destination directory if it doesn't exist + auto dataDirectory = ServerPathUtils::getDataDirectory(); + if (QDir().mkpath(dataDirectory)) { + if (oldConfigFile.copy(_userConfigFilename)) { + qDebug() << "Migrated config file from" << oldConfigFilename << "to" << _userConfigFilename; + } else { + qWarning() << "Could not copy previous config file from" << oldConfigFilename << "to" << _userConfigFilename + << "- please try to copy manually and restart."; + } + } else { + qWarning() << "Could not create application data directory" << dataDirectory << "- unable to migrate previous config file."; + } + } + } + } loadMapFromJSONFile(_userConfig, _userConfigFilename); + mergeMasterAndUserConfigs(); +} + +void HifiConfigVariantMap::mergeMasterAndUserConfigs() { // the merged config is initially matched to the master config _mergedConfig = _masterConfig; diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h index 75b8be1984..e92561cff5 100644 --- a/libraries/shared/src/HifiConfigVariantMap.h +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -13,6 +13,7 @@ #define hifi_HifiConfigVariantMap_h #include +#include class HifiConfigVariantMap { public: @@ -25,6 +26,8 @@ public: QVariantMap& getUserConfig() { return _userConfig; } QVariantMap& getMergedConfig() { return _mergedConfig; } + void mergeMasterAndUserConfigs(); + const QString& getUserConfigFilename() const { return _userConfigFilename; } private: QString _userConfigFilename; diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 7dd1aceb3d..c1cf969f36 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -42,6 +42,8 @@ LogHandler::LogHandler() : const char* stringForLogType(LogMsgType msgType) { switch (msgType) { + case LogInfo: + return "INFO"; case LogDebug: return "DEBUG"; case LogWarning: diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index a74a6287d7..ee8e426c34 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -22,11 +22,12 @@ const int VERBOSE_LOG_INTERVAL_SECONDS = 5; enum LogMsgType { + LogInfo = QtInfoMsg, LogDebug = QtDebugMsg, LogWarning = QtWarningMsg, LogCritical = QtCriticalMsg, LogFatal = QtFatalMsg, - LogSuppressed + LogSuppressed = 100 }; /// Handles custom message handling and sending of stats/logs to Logstash instance diff --git a/libraries/shared/src/ServerPathUtils.cpp b/libraries/shared/src/ServerPathUtils.cpp new file mode 100644 index 0000000000..ca87a28610 --- /dev/null +++ b/libraries/shared/src/ServerPathUtils.cpp @@ -0,0 +1,37 @@ +// +// ServerPathUtils.cpp +// libraries/shared/src +// +// Created by Ryan Huffman on 01/12/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ServerPathUtils.h" + +#include +#include +#include +#include + +QString ServerPathUtils::getDataDirectory() { + auto dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + +#ifdef Q_OS_WIN + dataPath += "/AppData/Roaming/"; +#elif defined(Q_OS_OSX) + dataPath += "/Library/Application Support/"; +#else + dataPath += "/.local/share/"; +#endif + + dataPath += qApp->organizationName() + "/" + qApp->applicationName(); + + return QDir::cleanPath(dataPath); +} + +QString ServerPathUtils::getDataFilePath(QString filename) { + return QDir(getDataDirectory()).absoluteFilePath(filename); +} + diff --git a/libraries/shared/src/ServerPathUtils.h b/libraries/shared/src/ServerPathUtils.h new file mode 100644 index 0000000000..28a9a71f0d --- /dev/null +++ b/libraries/shared/src/ServerPathUtils.h @@ -0,0 +1,22 @@ +// +// ServerPathUtils.h +// libraries/shared/src +// +// Created by Ryan Huffman on 01/12/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ServerPathUtils_h +#define hifi_ServerPathUtils_h + +#include + +namespace ServerPathUtils { + QString getDataDirectory(); + QString getDataFilePath(QString filename); +} + +#endif // hifi_ServerPathUtils_h \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 298d5f669b..4c893e7074 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -685,3 +685,9 @@ bool similarStrings(const QString& stringA, const QString& stringB) { return similarity >= SIMILAR_ENOUGH; } +void disableQtBearerPoll() { + // to work around the Qt constant wireless scanning, set the env for polling interval very high + const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); + qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); +} + diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 89ccba1479..1fae3bcff6 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -172,4 +172,6 @@ uint qHash(const std::shared_ptr& ptr, uint seed = 0) return qHash(ptr.get(), seed); } +void disableQtBearerPoll(); + #endif // hifi_SharedUtil_h diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 2fea6b7db4..c03da7ff13 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -25,6 +25,37 @@ SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : _transform.setRotation(glm::quat()); } +const QUuid SpatiallyNestable::getID() const { + QUuid result; + _idLock.withReadLock([&] { + result = _id; + }); + return result; +} + +void SpatiallyNestable::setID(const QUuid& id) { + _idLock.withWriteLock([&] { + _id = id; + }); +} + +const QUuid SpatiallyNestable::getParentID() const { + QUuid result; + _idLock.withReadLock([&] { + result = _parentID; + }); + return result; +} + +void SpatiallyNestable::setParentID(const QUuid& parentID) { + _idLock.withWriteLock([&] { + if (_parentID != parentID) { + _parentID = parentID; + _parentKnowsMe = false; + } + }); +} + Transform SpatiallyNestable::getParentTransform(bool& success) const { Transform result; SpatiallyNestablePointer parent = getParentPointer(success); @@ -40,14 +71,15 @@ Transform SpatiallyNestable::getParentTransform(bool& success) const { SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) const { SpatiallyNestablePointer parent = _parent.lock(); + QUuid parentID = getParentID(); // used for its locking - if (!parent && _parentID.isNull()) { + if (!parent && parentID.isNull()) { // no parent success = true; return nullptr; } - if (parent && parent->getID() == _parentID) { + if (parent && parent->getID() == parentID) { // parent pointer is up-to-date if (!_parentKnowsMe) { parent->beParentOfChild(getThisPointer()); @@ -72,7 +104,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons success = false; return nullptr; } - _parent = parentFinder->find(_parentID, success); + _parent = parentFinder->find(parentID, success); if (!success) { return nullptr; } @@ -83,7 +115,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons _parentKnowsMe = true; } - if (parent || _parentID.isNull()) { + if (parent || parentID.isNull()) { success = true; } else { success = false; @@ -104,13 +136,6 @@ void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const { }); } -void SpatiallyNestable::setParentID(const QUuid& parentID) { - if (_parentID != parentID) { - _parentID = parentID; - _parentKnowsMe = false; - } -} - void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) { _parentJointIndex = parentJointIndex; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 75783fa6d5..2a0b58f368 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -36,10 +36,10 @@ public: SpatiallyNestable(NestableType nestableType, QUuid id); virtual ~SpatiallyNestable() { } - virtual const QUuid& getID() const { return _id; } - virtual void setID(const QUuid& id) { _id = id; } + virtual const QUuid getID() const; + virtual void setID(const QUuid& id); - virtual QUuid getParentID() const { return _parentID; } + virtual const QUuid getParentID() const; virtual void setParentID(const QUuid& parentID); virtual quint16 getParentJointIndex() const { return _parentJointIndex; } @@ -145,6 +145,7 @@ protected: private: mutable ReadWriteLockable _transformLock; + mutable ReadWriteLockable _idLock; Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform. mutable bool _parentKnowsMe { false }; bool _isDead { false }; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index e215fcc067..8f70931085 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -16,6 +16,8 @@ #include #include +#include "VrMenu.h" + // Needs to match the constants in resources/qml/Global.js class OffscreenFlags : public QObject { Q_OBJECT @@ -192,7 +194,7 @@ QMessageBox::StandardButton OffscreenUi::messageBox(QMessageBox::Icon icon, cons map.insert("buttons", buttons.operator int()); map.insert("defaultButton", defaultButton); QVariant result; - bool invokeResult = QMetaObject::invokeMethod(getDesktop(), "messageBox", + bool invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); @@ -231,16 +233,27 @@ void OffscreenUi::setNavigationFocused(bool focused) { offscreenFlags->setNavigationFocused(focused); } -void OffscreenUi::createDesktop() { +void OffscreenUi::createDesktop(const QUrl& url) { if (_desktop) { qDebug() << "Desktop already created"; + return; } +#ifdef DEBUG + getRootContext()->setContextProperty("DebugQML", QVariant(true)); +#else getRootContext()->setContextProperty("DebugQML", QVariant(false)); - _desktop = dynamic_cast(load("Root.qml")); +#endif + + _desktop = dynamic_cast(load(url)); Q_ASSERT(_desktop); - getRootContext()->setContextProperty("Desktop", _desktop); + getRootContext()->setContextProperty("desktop", _desktop); + + // Enable focus debugging _desktop->setProperty("offscreenWindow", QVariant::fromValue(getWindow())); + _toolWindow = _desktop->findChild("ToolWindow"); + + new VrMenu(this); } QQuickItem* OffscreenUi::getDesktop() { @@ -251,38 +264,15 @@ QQuickItem* OffscreenUi::getToolWindow() { return _toolWindow; } -Q_DECLARE_METATYPE(std::function); -static auto VoidLambdaType = qRegisterMetaType>(); -Q_DECLARE_METATYPE(std::function); -static auto VariantLambdaType = qRegisterMetaType>(); - - -void OffscreenUi::executeOnUiThread(std::function function) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "executeOnUiThread", Qt::QueuedConnection, - Q_ARG(std::function, function)); - return; - } - - function(); -} - -QVariant OffscreenUi::returnFromUiThread(std::function function) { - if (QThread::currentThread() != thread()) { - QVariant result; - QMetaObject::invokeMethod(this, "returnFromUiThread", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, result), - Q_ARG(std::function, function)); - return result; - } - - return function(); -} - void OffscreenUi::unfocusWindows() { bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows"); Q_ASSERT(invokeResult); } +void OffscreenUi::toggleMenu(const QPoint& screenPosition) { + auto virtualPos = mapToVirtualScreen(screenPosition, nullptr); + QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos)); +} + #include "OffscreenUi.moc" diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index fa4d7aaeaf..fd54bc6c2b 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -27,18 +27,18 @@ class OffscreenUi : public OffscreenQmlSurface, public Dependency { public: OffscreenUi(); virtual void create(QOpenGLContext* context) override; - void createDesktop(); + void createDesktop(const QUrl& url); void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); bool shouldSwallowShortcut(QEvent* event); bool navigationFocused(); void setNavigationFocused(bool focused); void unfocusWindows(); + void toggleMenu(const QPoint& screenCoordinates); + QQuickItem* getDesktop(); QQuickItem* getToolWindow(); - Q_INVOKABLE void executeOnUiThread(std::function function); - Q_INVOKABLE QVariant returnFromUiThread(std::function function); /// Same design as QMessageBox::critical(), will block, returns result static QMessageBox::StandardButton critical(void* ignored, const QString& title, const QString& text, diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 4a827f57db..3874b85a12 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -345,7 +345,7 @@ void QmlWindowClass::hasClosed() { } void QmlWindowClass::raise() { - QMetaObject::invokeMethod(asQuickItem(), "raiseWindow", Qt::QueuedConnection); + QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::QueuedConnection); } #include "QmlWindowClass.moc" diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 2d9dd57ba9..103eb7660e 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -36,7 +36,6 @@ public: private: MenuUserData(const MenuUserData&); - void init(QObject* widgetObject, QObject* qmlObject) { widgetObject->setUserData(USER_DATA_ID, this); qmlObject->setUserData(USER_DATA_ID, this); @@ -48,14 +47,6 @@ private: const int MenuUserData::USER_DATA_ID = QObject::registerUserData(); -HIFI_QML_DEF_LAMBDA(VrMenu, [&](QQmlContext* context, QObject* newItem) { - auto offscreenUi = DependencyManager::get(); - QObject* rootMenu = offscreenUi->getRootItem()->findChild("rootMenu"); - Q_ASSERT(rootMenu); - static_cast(newItem)->setRootMenu(rootMenu); - context->setContextProperty("rootMenu", rootMenu); -}); - VrMenu* VrMenu::_instance{ nullptr }; static QQueue> queuedLambdas; @@ -70,19 +61,18 @@ void VrMenu::executeOrQueue(std::function f) { } } -void VrMenu::executeQueuedLambdas() { - Q_ASSERT(_instance); + +VrMenu::VrMenu(QObject* parent) : QObject(parent) { + _instance = this; + auto offscreenUi = DependencyManager::get(); + _rootMenu = offscreenUi->getRootItem()->findChild("rootMenu"); + offscreenUi->getRootContext()->setContextProperty("rootMenu", _rootMenu); foreach(std::function f, queuedLambdas) { - f(_instance); + f(this); } queuedLambdas.clear(); } -VrMenu::VrMenu(QQuickItem* parent) : QQuickItem(parent) { - _instance = this; - this->setEnabled(false); -} - QObject* VrMenu::findMenuObject(const QString& menuOption) { if (menuOption.isEmpty()) { return _rootMenu; @@ -91,10 +81,6 @@ QObject* VrMenu::findMenuObject(const QString& menuOption) { return result; } -void VrMenu::setRootMenu(QObject* rootMenu) { - _rootMenu = rootMenu; -} - void updateQmlItemFromAction(QObject* target, QAction* source) { target->setProperty("checkable", source->isCheckable()); target->setProperty("enabled", source->isEnabled()); @@ -116,9 +102,8 @@ void VrMenu::addMenu(QMenu* menu) { Q_ASSERT(false); } QVariant returnedValue; - bool invokeResult = QMetaObject::invokeMethod(this, "addMenu", Qt::DirectConnection, + bool invokeResult = QMetaObject::invokeMethod(qmlParent, "addMenu", Qt::DirectConnection, Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, QVariant::fromValue(qmlParent)), Q_ARG(QVariant, QVariant::fromValue(menu->title()))); Q_ASSERT(invokeResult); Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x @@ -147,21 +132,24 @@ void bindActionToQmlAction(QObject* qmlAction, QAction* action) { QObject::connect(qmlAction, SIGNAL(triggered()), action, SLOT(trigger())); } +class QQuickMenuItem; + void VrMenu::addAction(QMenu* menu, QAction* action) { Q_ASSERT(!MenuUserData::forObject(action)); Q_ASSERT(MenuUserData::forObject(menu)); MenuUserData* userData = MenuUserData::forObject(menu); QObject* menuQml = findMenuObject(userData->uuid.toString()); Q_ASSERT(menuQml); - QVariant returnedValue; + QQuickMenuItem* returnedValue { nullptr }; - bool invokeResult = QMetaObject::invokeMethod(this, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, QVariant::fromValue(menuQml)), - Q_ARG(QVariant, QVariant::fromValue(action->text()))); + qDebug() << menuQml; + bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItem", Qt::DirectConnection, + Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_ARG(QString, action->text())); + Q_ASSERT(invokeResult); Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x - QObject* result = returnedValue.value(); + QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); // Bind the QML and Widget together bindActionToQmlAction(result, action); @@ -175,19 +163,19 @@ void VrMenu::insertAction(QAction* before, QAction* action) { beforeQml = findMenuObject(beforeUserData->uuid.toString()); } QObject* menu = beforeQml->parent(); - QVariant returnedValue; - bool invokeResult = QMetaObject::invokeMethod(this, "insertItem", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, QVariant::fromValue(menu)), - Q_ARG(QVariant, QVariant::fromValue(beforeQml)), - Q_ARG(QVariant, QVariant::fromValue(action->text()))); + QQuickMenuItem* returnedValue { nullptr }; + // FIXME this needs to find the index of the beforeQml item and call insertItem(int, object) + bool invokeResult = QMetaObject::invokeMethod(menu, "addItem", Qt::DirectConnection, + Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_ARG(QString, action->text())); Q_ASSERT(invokeResult); - Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x - QObject* result = returnedValue.value(); + QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); bindActionToQmlAction(result, action); } +class QQuickMenuBase; + void VrMenu::removeAction(QAction* action) { if (!action) { qWarning("Attempted to remove invalid menu action"); @@ -198,12 +186,12 @@ void VrMenu::removeAction(QAction* action) { qWarning("Attempted to remove menu action with no found QML object"); return; } + QObject* item = findMenuObject(userData->uuid.toString()); QObject* menu = item->parent(); // Proxy QuickItem requests through the QML layer - bool invokeResult = QMetaObject::invokeMethod(this, "removeItem", Qt::DirectConnection, - Q_ARG(QVariant, QVariant::fromValue(menu)), - Q_ARG(QVariant, QVariant::fromValue(item))); + QQuickMenuBase* qmlItem = reinterpret_cast(item); + bool invokeResult = QMetaObject::invokeMethod(menu, "removeItem", Qt::DirectConnection, + Q_ARG(QQuickMenuBase*, qmlItem)); Q_ASSERT(invokeResult); - Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x } diff --git a/libraries/ui/src/VrMenu.h b/libraries/ui/src/VrMenu.h index 38e3b54739..b092b63566 100644 --- a/libraries/ui/src/VrMenu.h +++ b/libraries/ui/src/VrMenu.h @@ -21,21 +21,16 @@ #include "OffscreenUi.h" // FIXME break up the rendering code (VrMenu) and the code for mirroring a Widget based menu in QML -class VrMenu : public QQuickItem { +class VrMenu : public QObject { Q_OBJECT - HIFI_QML_DECL_LAMBDA - public: static void executeOrQueue(std::function f); - static void executeQueuedLambdas(); - VrMenu(QQuickItem* parent = nullptr); + VrMenu(QObject* parent = nullptr); void addMenu(QMenu* menu); void addAction(QMenu* parent, QAction* action); void insertAction(QAction* before, QAction* action); void removeAction(QAction* action); - void setRootMenu(QObject* rootMenu); - protected: QObject* _rootMenu{ nullptr }; QObject* findMenuObject(const QString& name); diff --git a/server-console/.gitignore b/server-console/.gitignore new file mode 100644 index 0000000000..1624823e80 --- /dev/null +++ b/server-console/.gitignore @@ -0,0 +1,5 @@ +Server\ Console-*/ +server-console-*/ +electron-packager/ +npm-debug.log +logs/ diff --git a/server-console/CMakeLists.txt b/server-console/CMakeLists.txt new file mode 100644 index 0000000000..9a96b07442 --- /dev/null +++ b/server-console/CMakeLists.txt @@ -0,0 +1,74 @@ +set(TARGET_NAME packaged-server-console) + +if (PRODUCTION_BUILD) + set(PRODUCTION_OPTION "--production") +endif() + +# add a target that will package the console +add_custom_target(${TARGET_NAME}-npm-install + COMMAND npm install + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +add_custom_target(${TARGET_NAME} + COMMAND npm run packager -- --out ${CMAKE_CURRENT_BINARY_DIR} ${PRODUCTION_OPTION} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${TARGET_NAME}-npm-install +) + +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Server Console") +set_target_properties(${TARGET_NAME}-npm-install PROPERTIES FOLDER "hidden/Server Console") + +# add a dependency from the package target to the server components +add_dependencies(${TARGET_NAME} assignment-client domain-server) + +# set the packaged console folder depending on platform, so we can copy it +if (APPLE) + set(PACKAGED_CONSOLE_FOLDER "Server\\ Console-darwin-x64/${CONSOLE_EXEC_NAME}") +elseif (WIN32) + set(PACKAGED_CONSOLE_FOLDER "server-console-win32-x64") +elseif (UNIX) + set(PACKAGED_CONSOLE_FOLDER "server-console-linux-x64") +endif () + +# install the packaged Server Console in our install directory +if (APPLE) + install( + PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_CONSOLE_FOLDER}" + DESTINATION ${CONSOLE_INSTALL_DIR} + COMPONENT ${SERVER_COMPONENT} + ) +elseif (WIN32) + set(CONSOLE_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_CONSOLE_FOLDER}") + + install( + DIRECTORY "${CONSOLE_DESTINATION}/" + DESTINATION ${CONSOLE_INSTALL_DIR} + COMPONENT ${SERVER_COMPONENT} + ) + + # sign the copied server console executable after install + set(EXECUTABLE_PATH "${CONSOLE_DESTINATION}/${CONSOLE_EXEC_NAME}") + optional_win_executable_signing() +endif() + +if (PR_BUILD OR PRODUCTION_BUILD) + set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL FALSE EXCLUDE_FROM_DEFAULT_BUILD FALSE) + + if (APPLE) + set(BESIDE_CONSOLE_DIR "${CONSOLE_INSTALL_APP_PATH}/Contents/Resources") + else () + set(BESIDE_CONSOLE_DIR ${CONSOLE_INSTALL_DIR}) + endif () + + # configure our build info json file and install it beside the console + set(CONSOLE_BUILD_INFO_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build-info.json") + configure_file("${HF_CMAKE_DIR}/templates/console-build-info.json.in" ${CONSOLE_BUILD_INFO_OUTPUT}) + install( + FILES ${CONSOLE_BUILD_INFO_OUTPUT} + DESTINATION ${BESIDE_CONSOLE_DIR} + COMPONENT ${SERVER_COMPONENT} + ) +else () + set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) + set_target_properties(${TARGET_NAME}-npm-install PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) +endif () diff --git a/server-console/README.md b/server-console/README.md new file mode 100644 index 0000000000..b4b6c3533b --- /dev/null +++ b/server-console/README.md @@ -0,0 +1,25 @@ +### Console + +The High Fidelity Server Console, made with [Electron](http://electron.atom.io/). + +### Running Locally + +Make sure you have [Node.js](https://nodejs.org/en/) installed. Use the latest stable version. + +``` +npm install +npm start +``` + +To run, the console needs to find a build of Interface, domain-server, and assignment-client. + +The command `npm start` tells the console to look for builds of those binaries in a build folder beside this console folder. + +On platforms with separate build folders for release and debug libraries `npm start` will choose the debug binaries. On those platforms if you prefer to use local release builds you'll want `npm run local-release`. + +### Packaging + +CMake produces a target `packaged-server-console` that will bundle up everything you need for the Server Console on your platform. +It ensures that there are available builds for the domain-server and assignment-client. Then it produces an executable for the Server Console. + +The install target will copy all of the produced executables to a directory, ready for testing or packaging for deployment. diff --git a/server-console/package.json b/server-console/package.json new file mode 100644 index 0000000000..44f7b3d05e --- /dev/null +++ b/server-console/package.json @@ -0,0 +1,38 @@ +{ + "name": "hf-console", + "description": "High Fidelity Console", + "author": "High Fidelity", + "license": "Apache-2.0", + "version": "1.0.0", + "keywords": [ + "" + ], + "devDependencies": { + "electron-compilers": "^1.0.1", + "electron-packager": "^5.2.1", + "electron-prebuilt": "0.35.4" + }, + "repository": { + "type": "git", + "url": "https://github.com/highfidelity/hifi.git" + }, + "main": "src/main.js", + "scripts": { + "start": "electron . --binary-type local-debug --debug", + "local-release": "electron . --binary-type local-release --debug", + "local-release-no-debug": "electron . --binary-type local-release", + "packager": "node packager.js" + }, + "dependencies": { + "always-tail": "0.2.0", + "cheerio": "^0.19.0", + "extend": "^3.0.0", + "fs-extra": "^0.26.4", + "node-notifier": "^4.4.0", + "os-homedir": "^1.0.1", + "request": "2.67.0", + "request-progress": "1.0.2", + "unzip": "0.1.11", + "yargs": "^3.30.0" + } +} diff --git a/server-console/packager.js b/server-console/packager.js new file mode 100644 index 0000000000..bf8ddd68d4 --- /dev/null +++ b/server-console/packager.js @@ -0,0 +1,59 @@ +var packager = require('electron-packager') +var osType = require('os').type(); + +var platform = null; +if (osType == "Darwin" || osType == "Linux") { + platform = osType.toLowerCase(); +} else if (osType == "Windows_NT") { + platform = "win32" +} + +var argv = require('yargs').argv; + +// check which icon we should use, beta or regular +var iconName = argv.production ? "console" : "console-beta"; + +// setup the common options for the packager +var options = { + dir: __dirname, + name: "server-console", + version: "0.35.4", + overwrite: true, + prune: true, + arch: "x64", + platform: platform, + icon: "resources/" + iconName, + ignore: "logs|(S|s)erver(\\s|-)(C|c)onsole-\\S+|electron-packager|README.md|CMakeLists.txt|packager.js|.gitignore" +} + +const EXEC_NAME = "server-console"; +const SHORT_NAME = "Server Console"; +const FULL_NAME = "High Fidelity Server Console"; + +// setup per OS options +if (osType == "Darwin") { + options["app-bundle-id"] = "com.highfidelity.server-console" + (argv.production ? "" : "-dev") + options["name"] = SHORT_NAME +} else if (osType == "Windows_NT") { + options["version-string"] = { + CompanyName: "High Fidelity, Inc.", + FileDescription: SHORT_NAME, + ProductName: FULL_NAME, + OriginalFilename: EXEC_NAME + ".exe" + } +} + +// check if we were passed a custom out directory, pass it along if so +if (argv.out) { + options.out = argv.out +} + +// call the packager to produce the executable +packager(options, function(error, appPath) { + if (error) { + console.error("There was an error writing the packaged console: " + error.message); + process.exit(1); + } else { + console.log("Wrote new app to " + appPath); + } +}); diff --git a/server-console/resources/console-beta.icns b/server-console/resources/console-beta.icns new file mode 100644 index 0000000000..a0c2af881b Binary files /dev/null and b/server-console/resources/console-beta.icns differ diff --git a/server-console/resources/console-beta.ico b/server-console/resources/console-beta.ico new file mode 100644 index 0000000000..90962c4871 Binary files /dev/null and b/server-console/resources/console-beta.ico differ diff --git a/server-console/resources/console-beta.png b/server-console/resources/console-beta.png new file mode 100644 index 0000000000..4fa8e1ce80 Binary files /dev/null and b/server-console/resources/console-beta.png differ diff --git a/server-console/resources/console-notification.png b/server-console/resources/console-notification.png new file mode 100644 index 0000000000..0fd22b8900 Binary files /dev/null and b/server-console/resources/console-notification.png differ diff --git a/server-console/resources/console-tray-Template.png b/server-console/resources/console-tray-Template.png new file mode 100644 index 0000000000..11dbbbc38f Binary files /dev/null and b/server-console/resources/console-tray-Template.png differ diff --git a/server-console/resources/console-tray-Template@2x.png b/server-console/resources/console-tray-Template@2x.png new file mode 100644 index 0000000000..8acd094606 Binary files /dev/null and b/server-console/resources/console-tray-Template@2x.png differ diff --git a/server-console/resources/console-tray.ico b/server-console/resources/console-tray.ico new file mode 100644 index 0000000000..becc1b8e8b Binary files /dev/null and b/server-console/resources/console-tray.ico differ diff --git a/server-console/resources/console-tray.png b/server-console/resources/console-tray.png new file mode 100644 index 0000000000..6ff889828c Binary files /dev/null and b/server-console/resources/console-tray.png differ diff --git a/server-console/resources/console-tray@2x.png b/server-console/resources/console-tray@2x.png new file mode 100644 index 0000000000..7295647c20 Binary files /dev/null and b/server-console/resources/console-tray@2x.png differ diff --git a/server-console/resources/console.icns b/server-console/resources/console.icns new file mode 100644 index 0000000000..45dd334a3e Binary files /dev/null and b/server-console/resources/console.icns differ diff --git a/server-console/resources/console.ico b/server-console/resources/console.ico new file mode 100644 index 0000000000..872a4a7a68 Binary files /dev/null and b/server-console/resources/console.ico differ diff --git a/server-console/resources/console.png b/server-console/resources/console.png new file mode 100644 index 0000000000..a8260f9687 Binary files /dev/null and b/server-console/resources/console.png differ diff --git a/server-console/src/downloader.css b/server-console/src/downloader.css new file mode 100644 index 0000000000..6c30d9e3fe --- /dev/null +++ b/server-console/src/downloader.css @@ -0,0 +1,145 @@ +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-Regular.ttf'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-ExtraLight.ttf'); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-SemiBold.ttf'); + font-weight: bold; + font-style: normal; +} + + +* { + font-family: "Raleway", "Open Sans", Arial, Helvetica, sans-serif; + line-height: 130%; +} + +body { + margin: 0; + padding: 0; + color: #808785; + margin: 0 auto; + text-align: center; + font-size: 13.5pt; + -webkit-touch-callout: none; -webkit-user-select: none; + cursor: default; + overflow: hidden; + font-variant-numeric: lining-nums; + -moz-font-feature-settings: "lnum"; + -webkit-font-feature-settings: "lnum"; + font-feature-settings: "lnum"; +} + +#error-message { + background-color: #F7F8F8; + color: #EB4C5F; + padding: 20px; + margin: 20px 120px 0 120px; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + cursor: text; +} + +progress { + height: 1px; + width: 300px; + -webkit-appearance: none; +} + +progress[value]::-webkit-progress-bar { + background-color: #DADADA; + /* border-radius: 2px; */ +} + +progress[value]::-webkit-progress-value { + background-color: #21B7D4; + /* border-radius: 2px; */ +} + +#progress-bytes { + font-weight: lighter; + color: #BBBBBB; + margin-top: 10px; +} + +#download-summary { + margin-top: 80px; +} + +.state { + padding-top: 100px; +} + +h1 { + font-size: 29pt; + font-weight: normal; +} + +#cancel-area { + margin-top: 100px; +} + +a:link, +a:visited, +a:hover, +a:active { + color: #B4B4B4; +} + +a:hover { + color: #2D88A4; +} + +.one { + opacity: 0; + animation: dot 2.3s infinite; + animation-delay: 0.0s; +} + +.two { + opacity: 0; + animation: dot 2.3s infinite; + animation-delay: 0.2s; +} + +.three { + opacity: 0; + animation: dot 2.3s infinite; + animation-delay: 0.3s; +} + +@-webkit-keyframes dot { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +@keyframes dot { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} \ No newline at end of file diff --git a/server-console/src/downloader.html b/server-console/src/downloader.html new file mode 100644 index 0000000000..a7ba7bd7b7 --- /dev/null +++ b/server-console/src/downloader.html @@ -0,0 +1,34 @@ + + + + High Fidelity + + + + +
+

Downloading...

+ +
10 MB / 4.0 MB
+
+ Sit tight, we are downloading content for your home +
+
+ +
+

Installing...

+ Just a moment +
+ +
+

Success!

+ You're all set. +
+ +
+

Houston...

+ An unfortunate error has occurred: +
+
+ + diff --git a/server-console/src/downloader.js b/server-console/src/downloader.js new file mode 100644 index 0000000000..f7e67f03ce --- /dev/null +++ b/server-console/src/downloader.js @@ -0,0 +1,52 @@ +function ready() { + console.log("Ready"); + + const electron = require('electron'); + const remote = require('remote'); + window.$ = require('./vendor/jquery/jquery-2.1.4.min.js'); + + $(".state").hide(); + + var currentState = null; + + function updateState(state, args) { + if (state == 'downloading') { + function formatBytes(size) { + return (size / 1000000).toFixed('2'); + } + $('#download-progress').attr('value', args.percentage * 100); + if (args.size !== null && args.size.transferred !== null && args.size.total !== null) { + var progressString = formatBytes(args.size.transferred) + "MB / " + formatBytes(args.size.total) + "MB"; + $('#progress-bytes').html(progressString); + } else { + $('#progress-bytes').html("Retrieving resources..."); + } + } else if (state == 'installing') { + } else if (state == 'complete') { + setTimeout(function() { + remote.getCurrentWindow().close(); + }, 2000); + } else if (state == 'error') { + $('#error-message').html(args.message); + } + + if (currentState != state) { + if (currentState) { + $('#state-' + currentState).hide(); + } + $('#state-' + state).show(); + currentState = state; + } + } + + electron.ipcRenderer.on('update', function(event, message) { + updateState(message.state, message.args); + }); + + // updateState('error', { message: "This is an error message", progress: 0.5 }); + // updateState('complete', { progress: 0 }); + // updateState('downloading', { percentage: 0.5, size: { total: 83040400, transferred: 500308} }); + + updateState('downloading', { percentage: 0, size: null }); + electron.ipcRenderer.send('ready'); +} diff --git a/server-console/src/images/console-hf-logo-2x.png b/server-console/src/images/console-hf-logo-2x.png new file mode 100644 index 0000000000..4c0afd6777 Binary files /dev/null and b/server-console/src/images/console-hf-logo-2x.png differ diff --git a/server-console/src/images/console-menubar-osx-2x.png b/server-console/src/images/console-menubar-osx-2x.png new file mode 100644 index 0000000000..e8b3c11e29 Binary files /dev/null and b/server-console/src/images/console-menubar-osx-2x.png differ diff --git a/server-console/src/images/console-menubar-windows-2x.png b/server-console/src/images/console-menubar-windows-2x.png new file mode 100644 index 0000000000..5057da17d4 Binary files /dev/null and b/server-console/src/images/console-menubar-windows-2x.png differ diff --git a/server-console/src/log.css b/server-console/src/log.css new file mode 100644 index 0000000000..d13a48b5a0 --- /dev/null +++ b/server-console/src/log.css @@ -0,0 +1,87 @@ +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-Regular.ttf'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-ExtraLight.ttf'); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-SemiBold.ttf'); + font-weight: bold; + font-style: normal; +} + +body { + font-family: "Raleway", "Open Sans", Arial, Helvetica, sans-serif; + font-size: 13.5pt; + line-height: 1.6; + margin: 0; + padding: 0; + font-variant-numeric: lining-nums; + -moz-font-feature-settings: "lnum"; + -webkit-font-feature-settings: "lnum"; + font-feature-settings: "lnum"; +} + +ul.tabs { + margin: 0; + padding: 0px; + list-style: none; +} + +ul.tabs li { + background: none; + color: #222; + display: inline-block; + padding: 10px 15px; + cursor: pointer; +} + +ul.tabs li.current { + background-color: #ededed; + color: #222; +} + +.tab-pane { + display: none; + background-color: #ededed; + width: 100%; + overflow: auto; + margin: 0; + padding: 0; + left: 0; +} + +.tab-pane.current { + display: inherit; +} + +.tab-content { + overflow: auto; + padding: 10px; + background-color: #ededed; +} + +.top { + height: 45px; +} + +.bottom { + position: absolute; + left: 0; + top: 45px; + bottom: 0; + right: 0; +} + +.search { + display: none; + float: right; + margin: 10px; +} \ No newline at end of file diff --git a/server-console/src/log.html b/server-console/src/log.html new file mode 100644 index 0000000000..f704f3e35f --- /dev/null +++ b/server-console/src/log.html @@ -0,0 +1,23 @@ + + + + Log + + + + + + +
    + + +
+ +
+
+
+
+ + diff --git a/server-console/src/log.js b/server-console/src/log.js new file mode 100644 index 0000000000..d7aae8a432 --- /dev/null +++ b/server-console/src/log.js @@ -0,0 +1,205 @@ +var remote = require('electron').remote; +var os = require('os'); +var Tail = require('always-tail'); + +function cleanPath(path) { + if (os.type() == "Windows_NT") { + // Fix path on Windows + while (path.indexOf('\\') >= 0) { + path = path.replace('\\', '/', 'g'); + } + } + return path; +} + +// a: array, b: array +// Returns: { add: [...], subtract: [...] } +// add: array of items in b but not a +// subtract: array of items in a but not b +function difference(a, b) { + var add = []; + var subtract = []; + for (var k of a) { + if (b.indexOf(k) == -1) { + // In a, but not in b + subtract.push(k); + } + } + for (k of b) { + if (a.indexOf(k) == -1) { + // In a, but not in b + add.push(k); + } + } + return { + add: add, + subtract: subtract + }; +} + +ready = function() { + window.$ = require('./vendor/jquery/jquery-2.1.4.min.js'); + + var domainServer = remote.getGlobal('domainServer'); + var acMonitor = remote.getGlobal('acMonitor'); + + var logWatchers = { + 'ds': { + }, + 'ac': { + } + }; + + function updateLogWatchers(stream, watchList, newLogFilesByPID) { + // Consolidate into a list of the log paths + var newLogFilePaths = []; + for (var pid in newLogFilesByPID) { + newLogFilePaths.push(newLogFilesByPID[pid].stdout); + newLogFilePaths.push(newLogFilesByPID[pid].stderr); + } + + var oldLogFilePaths = Object.keys(watchList); + var diff = difference(oldLogFilePaths, newLogFilePaths); + console.log('diff', diff); + // For each removed file, remove it from our watch list + diff.subtract.forEach(function(removedLogFilePath) { + watchList[removedLogFilePath].unwatch(); + delete watchList[removedLogFilePath]; + }); + diff.add.forEach(function(addedLogFilePath) { + var cleanFilePath = cleanPath(addedLogFilePath); + + var logTail = new Tail(cleanFilePath, '\n', { start: 0, interval: 500 }); + + logTail.on('line', function(msg) { + appendLogMessage(0, msg, stream); + }); + + logTail.on('error', function(error) { + console.log("ERROR:", error); + }); + + logTail.watch(); + + watchList[addedLogFilePath] = logTail; + console.log("Watching", cleanFilePath); + }); + } + + function updateLogFiles() { + // Get ds and ac logs from main application + var dsLogs = domainServer.getLogs(); + var acLogs = acMonitor.getLogs(); + + updateLogWatchers('ds', logWatchers.ds, dsLogs); + updateLogWatchers('ac', logWatchers.ac, acLogs); + } + + window.onbeforeunload = function(e) { + domainServer.removeListener('logs-updated', updateLogFiles); + acMonitor.removeListener('logs-updated', updateLogFiles); + }; + + domainServer.on('logs-updated', updateLogFiles); + acMonitor.on('logs-updated', updateLogFiles); + + + const maxLogLines = 2500; + const ipcRenderer = require('electron').ipcRenderer; + + var currentTab = 'domain-server'; + var tabStates = { + 'domain-server': { + atBottom: true, + size: 0 + }, + 'assignment-client': { + atBottom: true, + size: 0 + } + }; + function setCurrentTab(tabId) { + if (currentTab == tabId) { + return; + } + + var padding = 15; + $currentTab = $('#' + currentTab); + tabStates[currentTab].atBottom = $currentTab[0].scrollTop >= ($currentTab[0].scrollHeight - $currentTab.height() - (2 * padding)); + + currentTab = tabId; + $('ul.tabs li').removeClass('current'); + $('.tab-pane').removeClass('current'); + + $('li[data-tab=' + tabId + ']').addClass('current'); + var $pidLog = $("#" + tabId); + $pidLog.addClass('current'); + + if (tabStates[tabId].atBottom) { + $pidLog.scrollTop($pidLog[0].scrollHeight); + } + } + + $('ul.tabs li').click(function(){ + setCurrentTab($(this).attr('data-tab')); + }); + + setCurrentTab('domain-server'); + setCurrentTab('assignment-client'); + + var filter = ""; + + function shouldDisplayLogMessage(message) { + return !filter || message.toLowerCase().indexOf(filter) >= 0; + } + + function appendLogMessage(pid, msg, name) { + console.log(pid, msg, name); + var id = "pid-" + pid; + id = name == "ds" ? "domain-server" : "assignment-client"; + var $pidLog = $('#' + id); + + var size = ++tabStates[id].size; + if (size > maxLogLines) { + $pidLog.find('div.log-line:first').remove(); + removed = true; + } + + var wasAtBottom = false; + if (currentTab == id) { + var padding = 15; + wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height() - (2 * padding)); + } + + var $logLine = $('
').text(msg); + if (!shouldDisplayLogMessage(msg)) { + $logLine.hide(); + } + + $pidLog.append($logLine); + + if (wasAtBottom) { + $pidLog.scrollTop($pidLog[0].scrollHeight); + } + + } + + // handle filtering of table rows on input change + $('#search-input').on('input', function() { + filter = $(this).val().toLowerCase(); + if (filter == "") { + $('.log-line').show(); + } else { + $('.log-line').each(function(){ + // decide to hide or show the row if it matches the filter + if (filter && $(this).text().toLowerCase().indexOf(filter) == -1) { + $(this).hide(); + } else { + $(this).show(); + } + }); + } + }); + + updateLogFiles(); +}; diff --git a/server-console/src/main.js b/server-console/src/main.js new file mode 100644 index 0000000000..2e6b0e1e9b --- /dev/null +++ b/server-console/src/main.js @@ -0,0 +1,808 @@ +'use strict'; + +const electron = require('electron'); +const app = electron.app; // Module to control application life. +const BrowserWindow = electron.BrowserWindow; + +const notifier = require('node-notifier'); +const util = require('util'); +const dialog = electron.dialog; +const Menu = require('menu'); +const Tray = require('tray'); +const shell = require('shell'); +const os = require('os'); +const childProcess = require('child_process'); +const path = require('path'); +const fs = require('fs-extra'); +const Tail = require('always-tail'); +const http = require('http'); +const unzip = require('unzip'); + +const request = require('request'); +const progress = require('request-progress'); +const osHomeDir = require('os-homedir'); + +const updater = require('./modules/hf-updater.js'); + +const Config = require('./modules/config').Config; + +const hfprocess = require('./modules/hf-process.js'); +const Process = hfprocess.Process; +const ACMonitorProcess = hfprocess.ACMonitorProcess; +const ProcessStates = hfprocess.ProcessStates; +const ProcessGroup = hfprocess.ProcessGroup; +const ProcessGroupStates = hfprocess.ProcessGroupStates; + +const osType = os.type(); + +const appIcon = path.join(__dirname, '../resources/console.png'); + +const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Days +const LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/; + +function getBuildInfo() { + var buildInfoPath = null; + + if (osType == 'Windows_NT') { + buildInfoPath = path.join(path.dirname(process.execPath), 'build-info.json'); + } else if (osType == 'Darwin') { + var contentPath = ".app/Contents/"; + var contentEndIndex = __dirname.indexOf(contentPath); + + if (contentEndIndex != -1) { + // this is an app bundle + var appPath = __dirname.substring(0, contentEndIndex) + ".app"; + buildInfoPath = path.join(appPath, "/Contents/Resources/build-info.json"); + } + } + + const DEFAULT_BUILD_INFO = { releaseType: "", buildIdentifier: "dev" }; + var buildInfo = DEFAULT_BUILD_INFO; + + if (buildInfoPath) { + console.log('Build info path:', buildInfoPath); + try { + buildInfo = JSON.parse(fs.readFileSync(buildInfoPath)); + } catch (e) { + buildInfo = DEFAULT_BUILD_INFO; + } + } + + return buildInfo; +} + +const buildInfo = getBuildInfo(); + +console.log("build info", buildInfo); + +function getRootHifiDataDirectory() { + var organization = "High Fidelity"; + if (buildInfo.releaseType != "PRODUCTION") { + organization += ' - ' + buildInfo.buildIdentifier; + } + if (osType == 'Windows_NT') { + return path.resolve(osHomeDir(), 'AppData/Roaming', organization); + } else if (osType == 'Darwin') { + return path.resolve(osHomeDir(), 'Library/Application Support', organization); + } else { + return path.resolve(osHomeDir(), '.local/share/', organization); + } +} + +function getAssignmentClientResourcesDirectory() { + return path.join(getRootHifiDataDirectory(), '/assignment-client'); +} + +function getApplicationDataDirectory() { + return path.join(getRootHifiDataDirectory(), '/Server Console'); +} + +console.log("Root hifi directory is: ", getRootHifiDataDirectory()); + +const ipcMain = electron.ipcMain; + + +var isShuttingDown = false; +function shutdown() { + if (!isShuttingDown) { + var idx = 0; + + // if the home server is running, show a prompt before quit to ask if the user is sure + if (homeServer.state == ProcessGroupStates.STARTED) { + idx = dialog.showMessageBox({ + type: 'question', + buttons: ['Yes', 'No'], + title: 'Are you sure?', + message: 'Quitting will stop your Server Console and your Home domain will no longer be running.' + }); + } + + if (idx == 0) { + isShuttingDown = true; + + userConfig.save(configPath); + + if (logWindow) { + logWindow.close(); + } + if (homeServer) { + homeServer.stop(); + } + + updateTrayMenu(null); + + if (homeServer.state == ProcessGroupStates.STOPPED) { + // if the home server is already down, take down the server console now + app.quit(); + } else { + // if the home server is still running, wait until we get a state change or timeout + // before quitting the app + var timeoutID = setTimeout(app.quit, 5000); + homeServer.on('state-update', function(processGroup) { + if (processGroup.state == ProcessGroupStates.STOPPED) { + clearTimeout(timeoutID); + app.quit(); + } + }); + } + } + } +} + +function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { + console.log("Deleting old log files in " + directoryPath); + + var filenames = []; + try { + filenames = fs.readdirSync(directoryPath); + } catch (e) { + console.warn("Error reading contents of log file directory", e); + return; + } + + for (const filename of filenames) { + console.log("Checking", filename); + const absolutePath = path.join(directoryPath, filename); + var stat = null; + try { + stat = fs.statSync(absolutePath); + } catch (e) { + console.log("Error stat'ing file", absolutePath, e); + continue; + } + const curTime = Date.now(); + if (stat.isFile() && filename.search(filenameRegex) >= 0) { + const ageInSeconds = (curTime - stat.mtime.getTime()) / 1000.0; + if (ageInSeconds >= maxAgeInSeconds) { + console.log("\tDeleting:", filename, ageInSeconds); + try { + fs.unlinkSync(absolutePath); + } catch (e) { + if (e.code != 'EBUSY') { + console.warn("\tError deleting:", e); + } + } + } + } + } +} + +var logPath = path.join(getApplicationDataDirectory(), '/logs'); + +console.log("Log directory:", logPath); +console.log("Data directory:", getRootHifiDataDirectory()); + +const configPath = path.join(getApplicationDataDirectory(), 'config.json'); +var userConfig = new Config(); +userConfig.load(configPath); + +// print out uncaught exceptions in the console +process.on('uncaughtException', function(err) { + console.error(err); + console.error(err.stack); +}); + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // Someone tried to run a second instance, focus the window (if there is one) + return true; +}); + +if (shouldQuit) { + console.warn("Another instance of the Server Console is already running - this instance will quit."); + app.quit(); + return; +} + +// Check command line arguments to see how to find binaries +var argv = require('yargs').argv; +var pathFinder = require('./modules/path-finder.js'); + +var interfacePath = null; +var dsPath = null; +var acPath = null; + +var debug = argv.debug; + +var binaryType = argv.binaryType; + +interfacePath = pathFinder.discoveredPath("Interface", binaryType, buildInfo.releaseType); +dsPath = pathFinder.discoveredPath("domain-server", binaryType, buildInfo.releaseType); +acPath = pathFinder.discoveredPath("assignment-client", binaryType, buildInfo.releaseType); + +function binaryMissingMessage(displayName, executableName, required) { + var message = "The " + displayName + " executable was not found.\n"; + + if (required) { + message += "It is required for the Server Console to run.\n\n"; + } else { + message += "\n"; + } + + if (debug) { + message += "Please ensure there is a compiled " + displayName + " in a folder named build in this checkout.\n\n"; + message += "It was expected to be found at one of the following paths:\n"; + + var paths = pathFinder.searchPaths(executableName, argv.localReleaseBuilds); + message += paths.join("\n"); + } else { + message += "It is expected to be found beside this executable.\n"; + message += "You may need to re-install the Server Console."; + } + + return message; +} + +// if at this point any of the paths are null, we're missing something we wanted to find + +if (!dsPath) { + dialog.showErrorBox("Domain Server Not Found", binaryMissingMessage("domain-server", "domain-server", true)); + app.quit(); +} + +if (!acPath) { + dialog.showErrorBox("Assignment Client Not Found", binaryMissingMessage("assignment-client", "assignment-client", true)); + app.quit(); +} + +function openFileBrowser(path) { + // Add quotes around path + path = '"' + path + '"'; + if (osType == "Windows_NT") { + childProcess.exec('start "" ' + path); + } else if (osType == "Darwin") { + childProcess.exec('open ' + path); + } else if (osType == "Linux") { + childProcess.exec('xdg-open ' + path); + } +} + +// NOTE: this looks like it does nothing, but it's very important. +// Without it the default behaviour is to quit the app once all windows closed +// which is absolutely not what we want for a taskbar application. +app.on('window-all-closed', function() { +}); + +function startInterface(url) { + var argArray = []; + + // check if we have a url parameter to include + if (url) { + argArray = ["--url", url]; + } + + // create a new Interface instance - Interface makes sure only one is running at a time + var pInterface = new Process('interface', interfacePath, argArray); + pInterface.start(); +} + +var tray = null; +global.homeServer = null; +global.domainServer = null; +global.acMonitor = null; +global.userConfig = userConfig; + +var LogWindow = function(ac, ds) { + this.ac = ac; + this.ds = ds; + this.window = null; + this.acMonitor = null; + this.dsMonitor = null; +} +LogWindow.prototype = { + open: function() { + if (this.window) { + this.window.show(); + this.window.restore(); + return; + } + // Create the browser window. + this.window = new BrowserWindow({ width: 700, height: 500, icon: appIcon }); + this.window.loadURL('file://' + __dirname + '/log.html'); + + if (!debug) { + this.window.setMenu(null); + } + + this.window.on('closed', function() { + this.window = null; + }.bind(this)); + }, + close: function() { + if (this.window) { + this.window.close(); + } + } +}; + +function goHomeClicked() { + if (interfacePath) { + startInterface('hifi://localhost'); + } else { + // show an error to say that we can't go home without an interface instance + dialog.showErrorBox("Client Not Found", binaryMissingMessage("High Fidelity client", "Interface", false)); + } +} + +function stackManagerBasePath() { + var dataPath = 'High Fidelity/Stack Manager/resources'; + + if (process.platform == "win32") { + return path.resolve(osHomeDir(), 'AppData/Local', dataPath); + } else if (process.platform == "darwin") { + return path.resolve(osHomeDir(), 'Library/Application Support', dataPath); + } else { + return "" + } +} + +function isStackManagerContentPresent() { + var modelsPath = path.resolve(stackManagerBasePath(), 'models.json.gz'); + + try { + var stats = fs.lstatSync(modelsPath); + + if (stats.isFile()) { + console.log("Stack Manager entities file discovered at " + modelsPath) + // we found a content file + return true; + } + } catch (e) { + console.log("Stack Manager entities file not found at " + modelsPath); + } +} + +function promptToMigrateContent() { + dialog.showMessageBox({ + type: 'question', + buttons: ['Yes', 'No'], + title: 'Migrate Content', + message: 'Are you sure?\n\nThis will stop your home server and replace everything in your home with your content from Stack Manager.' + }, function(index) { + if (index == 0) { + if (homeServer.state != ProcessGroupStates.STOPPED) { + var stopThenMigrateCallback = function(processGroup) { + if (isShuttingDown) { + homeServer.removeListener('state-update', stopThenMigrateCallback); + } else if (processGroup.state == ProcessGroupStates.STOPPED) { + performContentMigration(); + + homeServer.removeListener('state-update', stopThenMigrateCallback); + } + }; + + homeServer.on('state-update', stopThenMigrateCallback); + homeServer.stop(); + + } else { + performContentMigration(); + } + } + }); +} + +function performContentMigration() { + // check if there is a models file to migrate + var modelsPath = path.resolve(stackManagerBasePath(), 'models.json.gz'); + + try { + var stats = fs.lstatSync(modelsPath); + } catch (e) { + // no entities file + dialog.showMessageBox({ + type: 'info', + buttons: ['OK'], + title: 'Models File Not Found', + message: 'There is no models file at ' + modelsPath + '\n\nStack Manager content migration can not proceed.' + }, null); + + return; + } + + function showMigrationCompletionDialog(copyError) { + if (!copyError) { + // show message for successful migration + dialog.showMessageBox({ + type: 'info', + buttons: ['OK'], + title: 'Migration Complete', + message: 'Your Stack Manager content has been migrated.\n\nYour home server will now be restarted.' + }, null); + } else { + // show error message for copy fail + dialog.showMessageBox({ + type: 'info', + buttons: ['OK'], + title: 'Migration Failed', + message: 'There was an error copying your Stack Manager content: ' + copyError + '\n\nPlease try again.' + }, null); + } + } + + // we have a models file, try and copy it + var newModelsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'entities/models.json.gz') + console.log("Copying Stack Manager entity file from " + modelsPath + " to " + newModelsPath); + + try { + fs.copySync(modelsPath, newModelsPath); + + // check if there are any assets to copy + var oldAssetsPath = path.resolve(stackManagerBasePath(), 'assets'); + + var assets = fs.readdirSync(oldAssetsPath); + + if (assets.length > 0) { + // assume this means the directory is not empty + // and that we should copy it + var newAssetsPath = path.resolve(getAssignmentClientResourcesDirectory(), 'assets'); + + console.log("Copying Stack Manager assets from " + oldAssetsPath + " to " + newAssetsPath); + + // attempt to copy the assets folder + fs.copySync(oldAssetsPath, newAssetsPath, { + preserveTimestamps: true + }); + } + + showMigrationCompletionDialog(null); + } catch (error) { + showMigrationCompletionDialog(error); + } + + homeServer.start(); +} + +var logWindow = null; + +function buildMenuArray(serverState) { + var menuArray = null; + + if (isShuttingDown) { + menuArray = [ + { + label: "Shutting down...", + enabled: false + } + ]; + } else { + menuArray = [ + { + label: 'Server - Stopped', + enabled: false + }, + { + type: 'separator' + }, + { + label: 'Go Home', + click: goHomeClicked, + enabled: false + }, + { + type: 'separator' + }, + { + label: 'Start Server', + click: function() { homeServer.restart(); } + }, + { + label: 'Stop Server', + visible: false, + click: function() { homeServer.stop(); } + }, + { + label: 'Settings', + click: function() { shell.openExternal('http://localhost:40100/settings'); }, + enabled: false + }, + { + label: 'View Logs', + click: function() { logWindow.open(); } + }, + { + type: 'separator' + }, + { + label: 'Share', + click: function() { shell.openExternal('http://localhost:40100/settings/?action=share') } + }, + { + type: 'separator' + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: function() { shutdown(); } + } + ]; + + var foundStackManagerContent = isStackManagerContentPresent(); + if (foundStackManagerContent) { + // add a separator and the stack manager content migration option + menuArray.splice(menuArray.length - 1, 0, { + label: 'Migrate Stack Manager Content', + click: function() { promptToMigrateContent(); } + }, { + type: 'separator' + }); + } + + updateMenuArray(menuArray, serverState); + } + + return menuArray; +} + +const GO_HOME_INDEX = 2; +const SERVER_LABEL_INDEX = 0; +const RESTART_INDEX = 4; +const STOP_INDEX = 5; +const SETTINGS_INDEX = 6; + +function updateMenuArray(menuArray, serverState) { + // update the tray menu state + var running = serverState == ProcessGroupStates.STARTED; + + var serverLabelItem = menuArray[SERVER_LABEL_INDEX]; + var restartItem = menuArray[RESTART_INDEX]; + + // Go Home is only enabled if running + menuArray[GO_HOME_INDEX].enabled = running; + + // Stop is only visible if running + menuArray[STOP_INDEX].visible = running; + + // Settings is only visible if running + menuArray[SETTINGS_INDEX].enabled = running; + + if (serverState == ProcessGroupStates.STARTED) { + serverLabelItem.label = "Server - Started"; + restartItem.label = "Restart Server"; + } else if (serverState == ProcessGroupStates.STOPPED) { + serverLabelItem.label = "Server - Stopped"; + restartItem.label = "Start Server"; + } else if (serverState == ProcessGroupStates.STOPPING) { + serverLabelItem.label = "Server - Stopping"; + + restartItem.label = "Restart Server"; + restartItem.enabled = false; + } +} + +function updateTrayMenu(serverState) { + if (tray) { + var menuArray = buildMenuArray(serverState); + tray.setContextMenu(Menu.buildFromTemplate(menuArray)); + if (isShuttingDown) { + tray.setToolTip('High Fidelity - Shutting Down'); + } + } +} + +const httpStatusPort = 60332; + +function maybeInstallDefaultContentSet(onComplete) { + // Check for existing AC data + const acResourceDirectory = getAssignmentClientResourcesDirectory(); + console.log("Checking for existence of " + acResourceDirectory); + var userHasExistingServerData = true; + try { + fs.accessSync(acResourceDirectory); + } catch (e) { + console.log(e); + userHasExistingServerData = false; + } + + if (userHasExistingServerData) { + console.log("User has existing data, suppressing downloader"); + onComplete(); + return; + } + + // Show popup + var window = new BrowserWindow({ + icon: appIcon, + width: 640, + height: 480, + center: true, + frame: true, + useContentSize: true, + resizable: false + }); + window.loadURL('file://' + __dirname + '/downloader.html'); + if (!debug) { + window.setMenu(null); + } + window.show(); + + window.on('closed', onComplete); + + electron.ipcMain.on('ready', function() { + console.log("got ready"); + function sendStateUpdate(state, args) { + // console.log(state, window, args); + window.webContents.send('update', { state: state, args: args }); + } + + var aborted = false; + + // Start downloading content set + var req = progress(request.get({ + url: "https://s3.amazonaws.com/hifi-public/homeset/ContentSet-Lounge.zip" + }, function(error, responseMessage, responseData) { + if (aborted) { + return; + } else if (error || responseMessage.statusCode != 200) { + var message = ''; + if (error) { + message = "Error contacting resource server."; + } else { + message = "Error downloading resources from server."; + } + sendStateUpdate('error', { + message: message + }); + } else { + sendStateUpdate('installing'); + } + }), { throttle: 250 }).on('progress', function(state) { + if (!aborted) { + // Update progress popup + sendStateUpdate('downloading', state); + } + }); + var unzipper = unzip.Extract({ + path: acResourceDirectory, + verbose: true + }); + unzipper.on('close', function() { + console.log("Done", arguments); + sendStateUpdate('complete'); + }); + unzipper.on('error', function (err) { + console.log("aborting"); + aborted = true; + req.abort(); + console.log("ERROR"); + sendStateUpdate('error', { + message: "Error installing resources." + }); + }); + req.pipe(unzipper); + + window.on('closed', function() { + if (currentState == 'downloading') { + req.abort(); + } + }); + + userConfig.set('hasRun', true); + }); +} + +function maybeShowSplash() { + var suppressSplash = userConfig.get('doNotShowSplash', false); + + if (!suppressSplash) { + const zoomFactor = 0.8; + var window = new BrowserWindow({ + icon: appIcon, + width: 1600 * zoomFactor, + height: 737 * zoomFactor, + center: true, + frame: true, + useContentSize: true, + zoomFactor: zoomFactor, + resizable: false + }); + window.loadURL('file://' + __dirname + '/splash.html'); + if (!debug) { + window.setMenu(null); + } + window.show(); + + window.webContents.on('new-window', function(e, url) { + e.preventDefault(); + require('shell').openExternal(url); + }); + } +} + +const trayFilename = (osType == "Darwin" ? "console-tray-Template.png" : "console-tray.png"); +const trayIcon = path.join(__dirname, '../resources/' + trayFilename); + +const notificationIcon = path.join(__dirname, '../resources/console-notification.png'); + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +app.on('ready', function() { + + if (app.dock) { + // hide the dock icon on OS X + app.dock.hide(); + } + + // Create tray icon + tray = new Tray(trayIcon); + tray.setToolTip('High Fidelity Server Console'); + + tray.on('click', function() { + tray.popUpContextMenu(tray.menu); + }); + + updateTrayMenu(ProcessGroupStates.STOPPED); + + maybeInstallDefaultContentSet(function() { + maybeShowSplash(); + + if (buildInfo.releaseType == 'PRODUCTION') { + var currentVersion = null; + try { + currentVersion = parseInt(buildInfo.buildIdentifier); + } catch (e) { + } + + if (currentVersion !== null) { + const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; + var hasShownUpdateNotification = false; + const updateChecker = new updater.UpdateChecker(currentVersion, CHECK_FOR_UPDATES_INTERVAL_SECONDS); + updateChecker.on('update-available', function(latestVersion, url) { + if (!hasShownUpdateNotification) { + notifier.notify({ + icon: notificationIcon, + title: 'An update is available!', + message: 'High Fidelity version ' + latestVersion + ' is available', + wait: true, + url: url + }); + hasShownUpdateNotification = true; + } + }); + notifier.on('click', function(notifierObject, options) { + console.log("Got click", options.url); + shell.openExternal(options.url); + }); + } + } + + deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); + + if (dsPath && acPath) { + domainServer = new Process('domain-server', dsPath, ["--get-temp-name"], logPath); + acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n6', + '--log-directory', logPath, + '--http-status-port', httpStatusPort], httpStatusPort, logPath); + homeServer = new ProcessGroup('home', [domainServer, acMonitor]); + logWindow = new LogWindow(acMonitor, domainServer); + + var processes = { + home: homeServer + }; + + // handle process updates + homeServer.on('state-update', function(processGroup) { updateTrayMenu(processGroup.state); }); + + // start the home server + homeServer.start(); + } + }); +}); diff --git a/server-console/src/modules/config.js b/server-console/src/modules/config.js new file mode 100644 index 0000000000..df44dcfafe --- /dev/null +++ b/server-console/src/modules/config.js @@ -0,0 +1,45 @@ +var fs = require('fs'); +var extend = require('extend'); + +function Config() { + this.data = {}; +} +Config.prototype = { + load: function(filePath) { + var rawData = null; + try { + rawData = fs.readFileSync(filePath); + } catch(e) { + console.log("Config file not found"); + } + var configData = {}; + + try { + if (rawData) { + configData = JSON.parse(rawData); + } else { + configData = {}; + } + } catch(e) { + console.error("Error parsing config file", filePath) + } + + this.data = {}; + extend(true, this.data, configData); + }, + save: function(filePath) { + fs.writeFileSync(filePath, JSON.stringify(this.data)); + }, + get: function(key, defaultValue) { + if (this.data.hasOwnProperty(key)) { + return this.data[key]; + } + return defaultValue; + }, + set: function(key, value) { + console.log("Setting", key, "to", value); + this.data[key] = value; + } +}; + +exports.Config = Config; diff --git a/server-console/src/modules/hf-process.js b/server-console/src/modules/hf-process.js new file mode 100755 index 0000000000..c7f21ebbe5 --- /dev/null +++ b/server-console/src/modules/hf-process.js @@ -0,0 +1,352 @@ +'use strict' + +const request = require('request'); +const extend = require('extend'); +const util = require('util'); +const events = require('events'); +const childProcess = require('child_process'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); + +const ProcessGroupStates = { + STOPPED: 'stopped', + STARTED: 'started', + STOPPING: 'stopping' +}; + +const ProcessStates = { + STOPPED: 'stopped', + STARTED: 'started', + STOPPING: 'stopping' +}; + + + +function ProcessGroup(name, processes) { + events.EventEmitter.call(this); + + this.name = name; + this.state = ProcessGroupStates.STOPPED; + this.processes = []; + this.restarting = false; + + for (let process of processes) { + this.addProcess(process); + } +}; +util.inherits(ProcessGroup, events.EventEmitter); +ProcessGroup.prototype = extend(ProcessGroup.prototype, { + addProcess: function(process) { + this.processes.push(process); + process.on('state-update', this.onProcessStateUpdate.bind(this)); + }, + start: function() { + if (this.state != ProcessGroupStates.STOPPED) { + console.warn("Can't start process group that is not stopped."); + return; + } + + for (let process of this.processes) { + process.start(); + } + + this.state = ProcessGroupStates.STARTED; + this.emit('state-update', this); + }, + stop: function() { + if (this.state != ProcessGroupStates.STARTED) { + console.warn("Can't stop process group that is not started."); + return; + } + for (let process of this.processes) { + process.stop(); + } + this.state = ProcessGroupStates.STOPPING; + this.emit('state-update', this); + }, + restart: function() { + if (this.state == ProcessGroupStates.STOPPED) { + // start the group, we were already stopped + this.start(); + } else { + // set our restart flag so the group will restart once stopped + this.restarting = true; + + // call stop, that will put them in the stopping state + this.stop(); + } + }, + + // Event handlers + onProcessStateUpdate: function(process) { + var processesStillRunning = false; + for (let process of this.processes) { + if (process.state != ProcessStates.STOPPED) { + processesStillRunning = true; + break; + } + } + if (!processesStillRunning) { + this.state = ProcessGroupStates.STOPPED; + this.emit('state-update', this); + + // if we we're supposed to restart, call start now and reset the flag + if (this.restarting) { + this.start(); + this.restarting = false; + } + } + this.emit('process-update', process); + } +}); + +var ID = 0; +function Process(name, command, commandArgs, logDirectory) { + events.EventEmitter.call(this); + + this.id = ++ID; + this.name = name; + this.command = command; + this.commandArgs = commandArgs ? commandArgs : []; + this.child = null; + this.logDirectory = logDirectory; + this.logStdout = null; + this.logStderr = null; + + this.state = ProcessStates.STOPPED; +}; +util.inherits(Process, events.EventEmitter); +Process.prototype = extend(Process.prototype, { + start: function() { + if (this.state != ProcessStates.STOPPED) { + console.warn("Can't start process that is not stopped."); + return; + } + console.log("Starting " + this.command + " " + this.commandArgs.join(' ')); + + var logStdout = 'ignore', + logStderr = 'ignore'; + + if (this.logDirectory) { + var logDirectoryCreated = false; + + try { + fs.mkdirsSync(this.logDirectory); + logDirectoryCreated = true; + } catch (e) { + if (e.code == 'EEXIST') { + logDirectoryCreated = true; + } else { + console.error("Error creating log directory"); + } + } + + if (logDirectoryCreated) { + // Create a temporary file with the current time + var time = (new Date).getTime(); + var tmpLogStdout = path.resolve(this.logDirectory + '/' + this.name + '-' + time + '-stdout.txt'); + var tmpLogStderr = path.resolve(this.logDirectory + '/' + this.name + '-' + time + '-stderr.txt'); + + try { + logStdout = fs.openSync(tmpLogStdout, 'ax'); + } catch(e) { + console.log("Error creating stdout log file", e); + logStdout = 'ignore'; + } + try { + logStderr = fs.openSync(tmpLogStderr, 'ax'); + } catch(e) { + console.log("Error creating stderr log file", e); + logStderr = 'ignore'; + } + } + } + + try { + this.child = childProcess.spawn(this.command, this.commandArgs, { + detached: false, + stdio: ['ignore', logStdout, logStderr] + }); + } catch (e) { + console.log("Got error starting child process for " + this.name, e); + this.child = null; + this.updateState(ProcessStates.STOPPED); + return; + } + + if (logStdout != 'ignore') { + var pidLogStdout = path.resolve(this.logDirectory + '/' + this.name + "-" + this.child.pid + "-" + time + "-stdout.txt"); + fs.rename(tmpLogStdout, pidLogStdout, function(e) { + if (e !== null) { + console.log("Error renaming log file from " + tmpLogStdout + " to " + pidLogStdout, e); + } + }); + this.logStdout = pidLogStdout; + fs.closeSync(logStdout); + } + + if (logStderr != 'ignore') { + var pidLogStderr = path.resolve(this.logDirectory + '/' + this.name + "-" + this.child.pid + "-" + time + "-stderr.txt"); + fs.rename(tmpLogStderr, pidLogStderr, function(e) { + if (e !== null) { + console.log("Error renaming log file from " + tmpLogStdout + " to " + pidLogStdout, e); + } + }); + this.logStderr = pidLogStderr; + + fs.closeSync(logStderr); + } + + this.child.on('error', this.onChildStartError.bind(this)); + this.child.on('close', this.onChildClose.bind(this)); + + console.log("Child process started"); + this.updateState(ProcessStates.STARTED); + this.emit('logs-updated'); + }, + stop: function(force) { + if (this.state == ProcessStates.STOPPED) { + console.warn("Can't stop process that is not started or stopping."); + return; + } + if (os.type() == "Windows_NT") { + var command = "taskkill /pid " + this.child.pid; + if (force) { + command += " /f /t"; + } + childProcess.exec(command, {}, function(error) { + if (error) { + console.error('Error executing taskkill:', error); + } + }); + } else { + var signal = force ? 'SIGKILL' : null; + this.child.kill(signal); + } + + console.log("Stopping child process:", this.child.pid, this.name); + + if (!force) { + this.stoppingTimeoutID = setTimeout(function() { + if (this.state == ProcessStates.STOPPING) { + console.log("Force killling", this.name, this.child.pid); + this.stop(true); + } + }.bind(this), 2500); + } + + this.updateState(ProcessStates.STOPPING); + }, + updateState: function(newState) { + if (this.state != newState) { + this.state = newState; + this.emit('state-update', this); + return true; + } + return false; + }, + getLogs: function() { + var logs = {}; + logs[this.child.pid] = { + stdout: this.logStdout == 'ignore' ? null : this.logStdout, + stderr: this.logStderr == 'ignore' ? null : this.logStderr + }; + return logs; + }, + + // Events + onChildStartError: function(error) { + console.log("Child process error ", error); + this.updateState(ProcessStates.STOPPED); + }, + onChildClose: function(code) { + console.log("Child process closed with code ", code, this.name); + if (this.stoppingTimeoutID) { + clearTimeout(this.stoppingTimeoutID); + this.stoppingTimeoutID = null; + } + this.updateState(ProcessStates.STOPPED); + } +}); + +// ACMonitorProcess is an extension of Process that keeps track of the AC Montior's +// children status and log locations. +const CHECK_AC_STATUS_INTERVAL = 5000; +function ACMonitorProcess(name, path, args, httpStatusPort, logPath) { + Process.call(this, name, path, args, logPath); + + this.httpStatusPort = httpStatusPort; + + this.requestTimeoutID = null; + this.pendingRequest = null; + this.childServers = {}; +}; +util.inherits(ACMonitorProcess, Process); +ACMonitorProcess.prototype = extend(ACMonitorProcess.prototype, { + updateState: function(newState) { + if (ACMonitorProcess.super_.prototype.updateState.call(this, newState)) { + if (this.state == ProcessStates.STARTED) { + this._updateACMonitorStatus(); + } else { + if (this.requestTimeoutID) { + clearTimeout(this.requestTimeoutID); + this.requestTimeoutID = null; + } + if (this.pendingRequest) { + this.pendingRequest.destroy(); + this.pendingRequest = null; + } + } + } + }, + getLogs: function() { + var logs = {}; + logs[this.child.pid] = { + stdout: this.logStdout == 'ignore' ? null : this.logStdout, + stderr: this.logStderr == 'ignore' ? null : this.logStderr + }; + for (var pid in this.childServers) { + logs[pid] = { + stdout: this.childServers[pid].logStdout, + stderr: this.childServers[pid].logStderr + } + } + console.log(logs); + return logs; + }, + _updateACMonitorStatus: function() { + if (this.state != ProcessStates.STARTED) { + return; + } + + // If there is a pending request, return + if (this.pendingRequest) { + return; + } + + console.log("Checking AC Monitor status"); + var options = { + url: "http://localhost:" + this.httpStatusPort + "/status", + json: true + }; + this.pendingRequest = request(options, function(error, response, body) { + if (error) { + console.error('ERROR Getting AC Monitor status', error); + } else { + this.childServers = body.servers; + } + console.log(body); + + this.emit('logs-updated'); + + this.requestTimeoutID = setTimeout(this._updateACMonitorStatus.bind(this), CHECK_AC_STATUS_INTERVAL); + }.bind(this)); + } +}); + +module.exports.Process = Process; +module.exports.ACMonitorProcess = ACMonitorProcess; +module.exports.ProcessGroup = ProcessGroup; +module.exports.ProcessGroupStates = ProcessGroupStates; +module.exports.ProcessStates = ProcessStates; diff --git a/server-console/src/modules/hf-updater.js b/server-console/src/modules/hf-updater.js new file mode 100644 index 0000000000..38e409b3a2 --- /dev/null +++ b/server-console/src/modules/hf-updater.js @@ -0,0 +1,46 @@ +const request = require('request'); +const extend = require('extend'); +const util = require('util'); +const events = require('events'); +const cheerio = require('cheerio'); +const os = require('os'); + +const platform = os.type() == 'Windows_NT' ? 'windows' : 'mac'; + +const BUILDS_URL = 'https://highfidelity.com/builds.xml'; + +function UpdateChecker(currentVersion, checkForUpdatesEveryXSeconds) { + this.currentVersion = currentVersion; + console.log('cur', currentVersion); + + setInterval(this.checkForUpdates.bind(this), checkForUpdatesEveryXSeconds * 1000); + this.checkForUpdates(); +}; +util.inherits(UpdateChecker, events.EventEmitter); +UpdateChecker.prototype = extend(UpdateChecker.prototype, { + checkForUpdates: function() { + console.log("Checking for updates"); + request(BUILDS_URL, (error, response, body) => { + if (error) { + console.log("Error", error); + return; + } + if (response.statusCode == 200) { + try { + var $ = cheerio.load(body, { xmlMode: true }); + const latestBuild = $('project[name="interface"] platform[name="' + platform + '"]').children().first(); + const latestVersion = parseInt(latestBuild.find('version').text()); + console.log("Latest version is:", latestVersion, this.currentVersion); + if (latestVersion > this.currentVersion) { + const url = latestBuild.find('url').text(); + this.emit('update-available', latestVersion, url); + } + } catch (e) { + console.warn("Error when checking for updates", e); + } + } + }); + } +}); + +exports.UpdateChecker = UpdateChecker; diff --git a/server-console/src/modules/path-finder.js b/server-console/src/modules/path-finder.js new file mode 100644 index 0000000000..d4cb9aa277 --- /dev/null +++ b/server-console/src/modules/path-finder.js @@ -0,0 +1,85 @@ +var fs = require('fs'); +var path = require('path'); + +exports.searchPaths = function(name, binaryType, releaseType) { + function platformExtension(name) { + if (name == "Interface" || name == "High Fidelity") { + if (process.platform == "darwin") { + return ".app/Contents/MacOS/" + name + } else if (process.platform == "win32") { + return ".exe" + } else { + return "" + } + } else { + return process.platform == "win32" ? ".exe" : "" + } + } + + var extension = platformExtension(name); + var devBasePath = "../build/" + name + "/"; + + var paths = []; + + if (!releaseType) { + // check in the developer build tree for binaries + paths = [ + devBasePath + name + extension, + devBasePath + (binaryType == "local-release" ? "Release/" : "Debug/") + name + extension + ] + } else { + // check directly beside the binary + paths = [ + path.join(path.dirname(process.execPath), name + extension) + ]; + + // assume we're inside an app bundle on OS X + if (process.platform == "darwin") { + var contentPath = ".app/Contents/"; + var contentEndIndex = __dirname.indexOf(contentPath); + + if (contentEndIndex != -1) { + // this is an app bundle + var appPath = __dirname.substring(0, contentEndIndex) + ".app"; + + // check in Contents/MacOS for the binaries + var componentsPath = appPath + "/Contents/MacOS/Components.app/Contents/MacOS/"; + paths.push(componentsPath + name + extension); + + // check beside the app bundle for the binaries + paths.push(path.join(path.dirname(appPath), name + extension)); + } + } + } + + return paths; +} + +exports.discoveredPath = function (name, binaryType, releaseType) { + function binaryFromPaths(name, paths) { + for (var i = 0; i < paths.length; i++) { + var testPath = paths[i]; + + try { + var stats = fs.lstatSync(testPath); + + if (stats.isFile() || (stats.isDirectory() && extension == ".app")) { + console.log("Found " + name + " at " + testPath); + return testPath; + } + } catch (e) { + console.log("Executable with name " + name + " not found at path " + testPath); + } + } + + return null; + } + + // for a released server console on OS X, assume the name of the interface executable is "High Fidelity" + if (releaseType && process.platform == "darwin" && name == "Interface") { + name = "High Fidelity"; + } + + // attempt to find a binary at the usual paths, return null if it doesn't exist + return binaryFromPaths(name, this.searchPaths(name, binaryType, releaseType)); +} diff --git a/server-console/src/splash.css b/server-console/src/splash.css new file mode 100644 index 0000000000..bd3a06cbe3 --- /dev/null +++ b/server-console/src/splash.css @@ -0,0 +1,146 @@ +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-Regular.ttf'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-ExtraLight.ttf'); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-SemiBold.ttf'); + font-weight: bold; + font-style: normal; +} + +body { + height: 100%; + width: 100%; + margin: 0; + padding: 0; + color: #414141; + -webkit-touch-callout: none; + -webkit-user-select: none; + cursor: default; + font-variant-numeric: lining-nums; + -moz-font-feature-settings: "lnum"; + -webkit-font-feature-settings: "lnum"; + font-feature-settings: "lnum"; +} + +* { + font-family: "Raleway", "Open Sans", Arial, Helvetica, sans-serif; + font-size: 13.5pt; + line-height: 130%; +} + +a:link, +a:visited, +a:hover, +a:active { + font-weight: bold; + color: #2D88A4; +} + +a:hover { + font-weight: bold; + color: #00B4EF; +} + +h1 { + font-weight: normal; + color: #6D7472; + font-size: 30pt; + margin: 0; + padding: 0; +} + +h2 { + color: #6D7472; + font-weight: bold; + margin: 0; + padding: 0; +} + +.header-title { + padding-top: 80px; +} + +.content { + margin: 0 110px; +} + +.column { + display: inline-block; + float: left; + padding-top: 26px; +} + +.column.left { + width: 415px; + padding-right: 120px; +} + +.column.center { + width: 565px; +} + +.column.right { + z-index: -1; + float: right; + padding-top: 16px; + padding-left: 60px; + position: absolute; + right: -30px; +} + +.top { + height: 168px; + border-bottom: 2px solid #F5F6F6; +} + +.middle { + width: 1364px; + /* 1584 - (110 * 2) */ + position: absolute; + top: 168px; + bottom: 98px; +} + +.bottom { + width: 100%; + position: absolute; + height: 98px; + background-color: #F5F6F6; + bottom: 0; + /* padding-top: 34px; */ +} + +#main-content { + height: 350px; + border-bottom: 2px solid #F5F6F6; +} + +#existing-resources-area { + padding-top: 20px; +} + +.footer { + padding-top: 34px; +} + +.header-right { + /* float: right; */ + position: absolute; + top: 63px; + right: 114px; +} + +input[type="checkbox"] { + -webkit-transform: scale(1.4); + display: inline-block; +} \ No newline at end of file diff --git a/server-console/src/splash.html b/server-console/src/splash.html new file mode 100644 index 0000000000..30868ebb6f --- /dev/null +++ b/server-console/src/splash.html @@ -0,0 +1,86 @@ + + + + High Fidelity + + + + +
+
+

Hello Worlds!

+
+
+ +
+
+
+
+
+

+

What now?

+High Fidelity is now installed and your Home domain is ready for you to explore. To start you off, we've put a few things in your home to play around with and learn the ropes. +

+ +

+You can make your home yours by uploading your own models and scripts, and adding items from the Market. + +

+ +

+ To see how, check out 'The Basics' +

+
+
+

+

How do I use it?

+ You can manage your server by clicking on the High Fidelity icon in your + + In the menu that opens, you can: +

+ +

+

    +
  • go to your 'Home,' automatically launching High Fidelity
  • +
  • administer basic function like starting and stopping your server
  • +
  • access your server's settings and logs
  • +
+

+ +

+ For more information on managing your server, visit our documentation +

+
+
+ +
+
+
+

+

Your existing Stack Manager content is safe.

+ Server Console comes with demo content but does not overwrite your data. See our guide to importing content previously managed with Stack Manager. +

+
+
+
+ +
+ + diff --git a/server-console/src/splash.js b/server-console/src/splash.js new file mode 100644 index 0000000000..3d9bd81bde --- /dev/null +++ b/server-console/src/splash.js @@ -0,0 +1,11 @@ +var remote = require('electron').remote; + +ready = function() { + window.$ = require('./vendor/jquery/jquery-2.1.4.min.js'); + + var userConfig = remote.getGlobal('userConfig'); + $('#suppress-splash').change(function() { + console.log("updating"); + userConfig.set('doNotShowSplash', $(this).is(':checked')); + }); +} diff --git a/server-console/src/vendor/Raleway/OFL.txt b/server-console/src/vendor/Raleway/OFL.txt new file mode 100644 index 0000000000..1c9779ddcd --- /dev/null +++ b/server-console/src/vendor/Raleway/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2010, Matt McInerney (matt@pixelspread.com), +Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com), +Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/server-console/src/vendor/Raleway/Raleway-ExtraLight.ttf b/server-console/src/vendor/Raleway/Raleway-ExtraLight.ttf new file mode 100644 index 0000000000..281a001983 Binary files /dev/null and b/server-console/src/vendor/Raleway/Raleway-ExtraLight.ttf differ diff --git a/server-console/src/vendor/Raleway/Raleway-Regular.ttf b/server-console/src/vendor/Raleway/Raleway-Regular.ttf new file mode 100644 index 0000000000..252cad14a6 Binary files /dev/null and b/server-console/src/vendor/Raleway/Raleway-Regular.ttf differ diff --git a/server-console/src/vendor/Raleway/Raleway-SemiBold.ttf b/server-console/src/vendor/Raleway/Raleway-SemiBold.ttf new file mode 100644 index 0000000000..64b54a77a3 Binary files /dev/null and b/server-console/src/vendor/Raleway/Raleway-SemiBold.ttf differ diff --git a/server-console/src/vendor/jquery/jquery-2.1.4.min.js b/server-console/src/vendor/jquery/jquery-2.1.4.min.js new file mode 100644 index 0000000000..49990d6e14 --- /dev/null +++ b/server-console/src/vendor/jquery/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("