diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 3ff7eafd6b..1955b8f0c8 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -55,13 +55,7 @@ Agent::Agent(ReceivedMessage& message) : { DependencyManager::get()->setPacketSender(&_entityEditSender); - auto assetClient = DependencyManager::set(); - - QThread* assetThread = new QThread; - assetThread->setObjectName("Asset Thread"); - assetClient->moveToThread(assetThread); - connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init); - assetThread->start(); + ResourceManager::init(); DependencyManager::registerInheritance(); @@ -471,11 +465,7 @@ void Agent::aboutToFinish() { // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(nullptr); - // cleanup the AssetClient thread - QThread* assetThread = DependencyManager::get()->thread(); - DependencyManager::destroy(); - assetThread->quit(); - assetThread->wait(); + ResourceManager::cleanup(); // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 3a962d72d2..c425a239dd 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -139,13 +139,13 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : httpStatusPort = parser.value(httpStatusPortOption).toUShort(); } - QDir logDirectory { "." }; + QString 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(); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index f76434d9c7..322fe6e57e 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -33,8 +33,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType, QString assignmentPool, quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, - quint16 assignmentServerPort, quint16 httpStatusServerPort, QDir logDirectory) : - _logDirectory(logDirectory), + quint16 assignmentServerPort, quint16 httpStatusServerPort, QString logDirectory) : _httpManager(QHostAddress::LocalHost, httpStatusServerPort, "", this), _numAssignmentClientForks(numAssignmentClientForks), _minAssignmentClientForks(minAssignmentClientForks), @@ -48,6 +47,11 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen { qDebug() << "_requestAssignmentType =" << _requestAssignmentType; + if (!logDirectory.isEmpty()) { + _wantsChildFileLogging = true; + _logDirectory = QDir(logDirectory); + } + // start the Logging class with the parent's target name LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME); @@ -159,52 +163,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"; + QString nowString, stdoutFilenameTemp, stderrFilenameTemp, stdoutPathTemp, stderrPathTemp; - if (!_logDirectory.exists()) { - qDebug() << "Log directory (" << _logDirectory.absolutePath() << ") does not exist, creating."; - _logDirectory.mkpath(_logDirectory.absolutePath()); + + if (_wantsChildFileLogging) { + // 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()); + } + + nowString = QDateTime::currentDateTime().toString(DATETIME_FORMAT); + stdoutFilenameTemp = QString("ac-%1-stdout.txt").arg(nowString); + stderrFilenameTemp = QString("ac-%1-stderr.txt").arg(nowString); + stdoutPathTemp = _logDirectory.absoluteFilePath(stdoutFilenameTemp); + stderrPathTemp = _logDirectory.absoluteFilePath(stderrFilenameTemp); + + // reset our output and error files + assignmentClient->setStandardOutputFile(stdoutPathTemp); + assignmentClient->setStandardErrorFile(stderrPathTemp); } - 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); + QString stdoutPath, stderrPath; - qDebug() << "Renaming " << stdoutPathTemp << " to " << stdoutPath; - if (!_logDirectory.rename(stdoutFilenameTemp, stdoutFilename)) { - qDebug() << "Failed to rename " << stdoutFilenameTemp; - stdoutPath = stdoutPathTemp; - stdoutFilename = stdoutFilenameTemp; + if (_wantsChildFileLogging) { + + // 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()); + stdoutPath = _logDirectory.absoluteFilePath(stdoutFilename); + 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; } - 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 @@ -212,6 +225,7 @@ void AssignmentClientMonitor::spawnChildClient() { this, [this, pid]() { childProcessFinished(pid); }); qDebug() << "Spawned a child client with PID" << assignmentClient->processId(); + _childProcesses.insert(assignmentClient->processId(), { assignmentClient, stdoutPath, stderrPath }); } } diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index a5ae3cd1af..a7f69a559b 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -38,7 +38,7 @@ 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 httpStatusServerPort, QDir logDirectory); + quint16 assignmentServerPort, quint16 httpStatusServerPort, QString logDirectory); ~AssignmentClientMonitor(); void stopChildProcesses(); @@ -73,6 +73,8 @@ private: quint16 _assignmentServerPort; QMap _childProcesses; + + bool _wantsChildFileLogging { false }; }; #endif // hifi_AssignmentClientMonitor_h diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index b6223497e6..b177d2a9a0 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -277,14 +277,17 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio // set of stats to have, but we'd probably want a different data structure if we keep it very long. // Since this version uses a single shared QMap for all senders, there could be some lock contention // on this QWriteLocker -void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) { +void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) { QWriteLocker locker(&_viewerSendingStatsLock); - _viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited }; + _viewerSendingStats[sessionID][dataID] = { usecTimestampNow(), dataLastEdited }; } -void EntityServer::trackViewerGone(const QUuid& viewerNode) { +void EntityServer::trackViewerGone(const QUuid& sessionID) { QWriteLocker locker(&_viewerSendingStatsLock); - _viewerSendingStats.remove(viewerNode); + _viewerSendingStats.remove(sessionID); + if (_entitySimulation) { + _entitySimulation->clearOwnership(sessionID); + } } QString EntityServer::serverSubclassStats() { diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index cd603f44d8..74057bfa5d 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -27,6 +27,8 @@ struct ViewerSendingStats { quint64 lastEdited; }; +class SimpleEntitySimulation; + class EntityServer : public OctreeServer, public NewlyCreatedEntityHook { Q_OBJECT public: @@ -52,8 +54,8 @@ public: virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override; virtual QString serverSubclassStats() override; - virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) override; - virtual void trackViewerGone(const QUuid& viewerNode) override; + virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) override; + virtual void trackViewerGone(const QUuid& sessionID) override; public slots: void pruneDeletedEntities(); @@ -65,7 +67,7 @@ private slots: void handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode); private: - EntitySimulation* _entitySimulation; + SimpleEntitySimulation* _entitySimulation; QTimer* _pruneDeletedEntitiesTimer = nullptr; QReadWriteLock _viewerSendingStatsLock; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 31cab68cdf..aedf451924 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -236,10 +236,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) : { _averageLoopTime.updateAverage(0); qDebug() << "Octree server starting... [" << this << "]"; - - // make sure the AccountManager has an Auth URL for payment redemptions - - AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); } OctreeServer::~OctreeServer() { diff --git a/cmake/macros/InstallBesideConsole.cmake b/cmake/macros/InstallBesideConsole.cmake index 4c6087b877..318a7a3ffe 100644 --- a/cmake/macros/InstallBesideConsole.cmake +++ b/cmake/macros/InstallBesideConsole.cmake @@ -64,11 +64,10 @@ macro(install_beside_console) ) endif() + # set variables used by manual ssleay library copy + set(TARGET_INSTALL_DIR ${COMPONENT_INSTALL_DIR}) + set(TARGET_INSTALL_COMPONENT ${SERVER_COMPONENT}) + manually_install_openssl_for_qt() + endif () - - # set variables used by manual ssleay library copy - set(TARGET_INSTALL_DIR ${COMPONENT_INSTALL_DIR}) - set(TARGET_INSTALL_COMPONENT ${SERVER_COMPONENT}) - manually_install_ssl_eay() - endmacro() diff --git a/cmake/macros/ManuallyInstallOpenSSLForQt.cmake b/cmake/macros/ManuallyInstallOpenSSLForQt.cmake new file mode 100644 index 0000000000..ea91bbb83b --- /dev/null +++ b/cmake/macros/ManuallyInstallOpenSSLForQt.cmake @@ -0,0 +1,34 @@ +# +# ManuallyInstallOpenSSLforQt.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_openssl_for_qt) + + # Qt dynamically links OpenSSL if it can find it on the user's machine + # We want to avoid it being found somewhere random and have it not being a compatible version + # So even though we don't need the dynamic version of OpenSSL for our direct-use purposes + # we use this macro to include the two SSL DLLs with the targets using QtNetwork + if (WIN32) + # we have to call find_package(OpenSSL) here even though this target may not directly need it + find_package(OpenSSL REQUIRED) + + install( + FILES "${OPENSSL_DLL_PATH}/ssleay32.dll" + DESTINATION ${TARGET_INSTALL_DIR} + COMPONENT ${TARGET_INSTALL_COMPONENT} + ) + + install( + FILES "${OPENSSL_DLL_PATH}/libeay32.dll" + DESTINATION ${TARGET_INSTALL_DIR} + COMPONENT ${TARGET_INSTALL_COMPONENT} + ) + endif() + +endmacro() diff --git a/cmake/macros/ManuallyInstallSSLEay.cmake b/cmake/macros/ManuallyInstallSSLEay.cmake deleted file mode 100644 index 41e7e9eaf3..0000000000 --- a/cmake/macros/ManuallyInstallSSLEay.cmake +++ /dev/null @@ -1,28 +0,0 @@ -# -# 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/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 5cc48d2598..69b6c367ca 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -67,10 +67,7 @@ find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h HINTS ${_OPENSSL_ROOT_HINTS_AN if (WIN32 AND NOT CYGWIN) if (MSVC) - # /MD and /MDd are the standard values - if someone wants to use - # others, the libnames have to change here too - # use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b - # TODO: handle /MT and static lib + # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: # * MD for dynamic-release # * MDd for dynamic-debug @@ -81,20 +78,23 @@ 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 - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" + + # The Kitware FindOpenSSL module has been modified here by High Fidelity to look specifically for static libraries + + find_library(LIB_EAY_DEBUG NAMES libeay32MTd + ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" ) - find_library(LIB_EAY_RELEASE NAMES libeay32MD libeay32 - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" + find_library(LIB_EAY_RELEASE NAMES libeay32MT + ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" ) - find_library(SSL_EAY_DEBUG NAMES ssleay32MDd ssleay32d - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" + find_library(SSL_EAY_DEBUG NAMES ssleay32MTd + ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" ) - find_library(SSL_EAY_RELEASE NAMES ssleay32MD ssleay32 ssl - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC" + find_library(SSL_EAY_RELEASE NAMES ssleay32MT + ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" ) set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}") @@ -109,37 +109,6 @@ 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} - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "lib/MinGW" - ) - - mark_as_advanced(SSL_EAY LIB_EAY) - set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY}) - unset(LIB_EAY_NAMES) - unset(SSL_EAY_NAMES) - else () - # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues: - find_library(LIB_EAY NAMES libeay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_LIBDIR} PATH_SUFFIXES lib) - - find_library(SSL_EAY NAMES ssleay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_LIBDIR} PATH_SUFFIXES lib) - - mark_as_advanced(SSL_EAY LIB_EAY) - set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY}) endif() else() @@ -250,8 +219,4 @@ else () ) endif () -if (WIN32) - add_paths_to_fixup_libs(${OPENSSL_DLL_PATH}) -endif () - mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES OPENSSL_SEARCH_DIRS) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 7dc94421be..fae07ace45 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) { function createTemporaryDomain() { swal({ title: 'Create temporary place name', - text: "This will create a temporary place name and domain ID (valid for 30 days)" + text: "This will create a temporary place name and domain ID" + " so other users can easily connect to your domain.

" + "In order to make your domain reachable, this will also enable full automatic networking.", showCancelButton: true, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3e4ee7b758..94e79416a5 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -258,9 +258,26 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (onlyEditorsAreRezzers) { canRez = isAllowedEditor; } + + QUuid hintNodeID; + + // in case this is a node that's failing to connect + // double check we don't have a node whose sockets match exactly already in the list + limitedNodeList->eachNodeBreakable([&nodeConnection, &hintNodeID](const SharedNodePointer& node){ + if (node->getPublicSocket() == nodeConnection.publicSockAddr + && node->getLocalSocket() == nodeConnection.localSockAddr) { + // we have a node that already has these exact sockets - this occurs if a node + // is unable to connect to the domain + hintNodeID = node->getUUID(); + + return false; + } + + return true; + }); - // add the new node - SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); + // add the connecting node (or re-use the matched one from eachNodeBreakable above) + SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID); // set the edit rights for this user newNode->setIsAllowedEditor(isAllowedEditor); @@ -279,28 +296,29 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect return newNode; } -SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) { +SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection, + QUuid nodeID) { HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr; SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID); - QUuid nodeUUID; - if (connectedPeer) { // this user negotiated a connection with us via ICE, so re-use their ICE client ID - nodeUUID = nodeConnection.connectUUID; + nodeID = nodeConnection.connectUUID; if (connectedPeer->getActiveSocket()) { // set their discovered socket to whatever the activated socket on the network peer object was discoveredSocket = *connectedPeer->getActiveSocket(); } } else { - // we got a connectUUID we didn't recognize, just add the node with a new UUID - nodeUUID = QUuid::createUuid(); + // we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one + if (nodeID.isNull()) { + nodeID = QUuid::createUuid(); + } } auto limitedNodeList = DependencyManager::get(); - SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeConnection.nodeType, + SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType, nodeConnection.publicSockAddr, nodeConnection.localSockAddr); // So that we can send messages to this node at will - we need to activate the correct socket on this node now @@ -331,7 +349,6 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, QCryptographicHash::Sha256); if (rsaPublicKey) { - QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); int decryptResult = RSA_verify(NID_sha256, reinterpret_cast(usernameWithToken.constData()), usernameWithToken.size(), diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 2fc4be380c..c4ac32fabf 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -59,7 +59,8 @@ private: SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, const QByteArray& usernameSignature); - SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection); + SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection, + QUuid nodeID = QUuid()); bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9e13c8e6fa..f0df67a6f6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) { + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { // we either read a certificate and private key or were not passed one // and completed login or did not need to @@ -198,7 +198,6 @@ bool DomainServer::optionallySetupOAuth() { } AccountManager& accountManager = AccountManager::getInstance(); - accountManager.disableSettingsFilePersistence(); accountManager.setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); @@ -372,20 +371,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); } -bool DomainServer::didSetupAccountManagerWithAccessToken() { - if (AccountManager::getInstance().hasValidAccessToken()) { - // we already gave the account manager a valid access token - return true; - } - - return resetAccountManagerAccessToken(); -} - const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; bool DomainServer::resetAccountManagerAccessToken() { @@ -401,9 +392,13 @@ bool DomainServer::resetAccountManagerAccessToken() { if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { accessToken = accessTokenVariant->toString(); } else { - qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present." - << "Set an access token via the web interface, in your user or master config" + qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."; + qDebug() << "Set an access token via the web interface, in your user or master config" << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; + + // clear any existing access token from AccountManager + AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString()); + return false; } } else { @@ -429,34 +424,6 @@ bool DomainServer::resetAccountManagerAccessToken() { } } -bool DomainServer::optionallySetupAssignmentPayment() { - const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments"; - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - - if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) && - settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() && - didSetupAccountManagerWithAccessToken()) { - - qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); - - // assume that the fact we are authing against HF data server means we will pay for assignments - // setup a timer to send transactions to pay assigned nodes every 30 seconds - QTimer* creditSetupTimer = new QTimer(this); - connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - - const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; - creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); - - QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); - - const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; - nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); - } - - return true; -} - void DomainServer::setupAutomaticNetworking() { auto nodeList = DependencyManager::get(); @@ -467,9 +434,9 @@ void DomainServer::setupAutomaticNetworking() { setupICEHeartbeatForFullNetworking(); } - if (!didSetupAccountManagerWithAccessToken()) { - qDebug() << "Cannot send heartbeat to data server without an access token."; - qDebug() << "Add an access token to your config file or via the web interface."; + if (!resetAccountManagerAccessToken()) { + qDebug() << "Will not send heartbeat to Metaverse API without an access token."; + qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; return; } @@ -526,6 +493,19 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { // we need this DS to know what our public IP is - start trying to figure that out now limitedNodeList->startSTUNPublicSocketUpdate(); + // to send ICE heartbeats we'd better have a private key locally with an uploaded public key + auto& accountManager = AccountManager::getInstance(); + auto domainID = accountManager.getAccountInfo().getDomainID(); + + // if we have an access token and we don't have a private key or the current domain ID has changed + // we should generate a new keypair + if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { + accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + } + + // hookup to the signal from account manager that tells us when keypair is available + connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); + if (!_iceHeartbeatTimer) { // setup a timer to heartbeat with the ice-server every so often _iceHeartbeatTimer = new QTimer { this }; @@ -1082,11 +1062,76 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainUpdateJSON.toUtf8()); } -// TODO: have data-web respond with ice-server hostname to use - void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { - DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); + + auto& accountManager = AccountManager::getInstance(); + auto limitedNodeList = DependencyManager::get(); + + if (!accountManager.getAccountInfo().hasPrivateKey()) { + qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; + qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; + + if (!limitedNodeList->getSessionUUID().isNull()) { + accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + } else { + qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported"; + } + + return; + } + + // NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with + // QDataStream and the possibility of IPv6 address for the sockets. + if (!_iceServerHeartbeatPacket) { + _iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); + } + + bool shouldRecreatePacket = false; + + if (_iceServerHeartbeatPacket->getPayloadSize() > 0) { + // if either of our sockets have changed we need to re-sign the heartbeat + // first read the sockets out from the current packet + _iceServerHeartbeatPacket->seek(0); + QDataStream heartbeatStream(_iceServerHeartbeatPacket.get()); + + QUuid senderUUID; + HifiSockAddr publicSocket, localSocket; + heartbeatStream >> senderUUID >> publicSocket >> localSocket; + + if (senderUUID != limitedNodeList->getSessionUUID() + || publicSocket != limitedNodeList->getPublicSockAddr() + || localSocket != limitedNodeList->getLocalSockAddr()) { + shouldRecreatePacket = true; + } + } else { + shouldRecreatePacket = true; + } + + if (shouldRecreatePacket) { + // either we don't have a heartbeat packet yet or some combination of sockets, ID and keypair have changed + // and we need to make a new one + + // reset the position in the packet before writing + _iceServerHeartbeatPacket->reset(); + + // write our plaintext data to the packet + QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get()); + heartbeatDataStream << limitedNodeList->getSessionUUID() + << limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr(); + + // setup a QByteArray that points to the plaintext data + auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize()); + + // generate a signature for the plaintext data in the packet + auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); + + // pack the signature with the data + heartbeatDataStream << signature; + } + + // send the heartbeat packet to the ice server now + limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket); } } @@ -1970,3 +2015,31 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer message) { + static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3; + + static int numHeartbeatDenials = 0; + if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) { + qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server" + << "- re-generating keypair now"; + + // we've hit our threshold of heartbeat denials, trigger a keypair re-generation + auto limitedNodeList = DependencyManager::get(); + AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + + // reset our number of heartbeat denials + numHeartbeatDenials = 0; + } +} + +void DomainServer::handleKeypairChange() { + if (_iceServerHeartbeatPacket) { + // reset the payload size of the ice-server heartbeat packet - this causes the packet to be re-generated + // the next time we go to send an ice-server heartbeat + _iceServerHeartbeatPacket->setPayloadSize(0); + + // send a heartbeat to the ice server immediately + sendHeartbeatToIceServer(); + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 326ca3e1a8..3a83e8696b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -61,6 +61,7 @@ public slots: void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); void processPathQueryPacket(QSharedPointer packet); void processNodeDisconnectRequestPacket(QSharedPointer message); + void processICEServerHeartbeatDenialPacket(QSharedPointer message); private slots: void aboutToQuit(); @@ -78,16 +79,16 @@ private slots: void handleTempDomainError(QNetworkReply& requestReply); void queuedQuit(QString quitMessage, int exitCode); + + void handleKeypairChange(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - bool optionallySetupAssignmentPayment(); void optionallyGetTemporaryName(const QStringList& arguments); - bool didSetupAccountManagerWithAccessToken(); bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); @@ -153,6 +154,7 @@ private: DomainServerSettingsManager _settingsManager; HifiSockAddr _iceServerSocket; + std::unique_ptr _iceServerHeartbeatPacket; QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer diff --git a/examples/away.js b/examples/away.js index 18a3fddfce..82b9c881c6 100644 --- a/examples/away.js +++ b/examples/away.js @@ -13,13 +13,30 @@ // // Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key. // See MAIN CONTROL, below, for what "paused" actually does. -var OVERLAY_RATIO = 1920 / 1080; +var OVERLAY_WIDTH = 1920; +var OVERLAY_HEIGHT = 1080; +var OVERLAY_RATIO = OVERLAY_WIDTH / OVERLAY_HEIGHT; var OVERLAY_DATA = { + width: OVERLAY_WIDTH, + height: OVERLAY_HEIGHT, imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", color: {red: 255, green: 255, blue: 255}, alpha: 1 }; +var lastOverlayPosition = { x: 0, y: 0, z: 0}; +var OVERLAY_DATA_HMD = { + position: lastOverlayPosition, + width: OVERLAY_WIDTH, + height: OVERLAY_HEIGHT, + url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1, + scale: 2, + isFacingAvatar: true, + drawInFront: true +}; + // ANIMATION // We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect // using an animation graph with a state that we turn on and off through the animation var defined with that state. @@ -64,29 +81,74 @@ function stopAwayAnimation() { // OVERLAY var overlay = Overlays.addOverlay("image", OVERLAY_DATA); +var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD); + +function moveCloserToCamera(positionAtHUD) { + // we don't actually want to render at the slerped look at... instead, we want to render + // slightly closer to the camera than that. + var MOVE_CLOSER_TO_CAMERA_BY = -0.25; + var cameraFront = Quat.getFront(Camera.orientation); + var closerToCamera = Vec3.multiply(cameraFront, MOVE_CLOSER_TO_CAMERA_BY); // slightly closer to camera + var slightlyCloserPosition = Vec3.sum(positionAtHUD, closerToCamera); + + return slightlyCloserPosition; +} + function showOverlay() { - var properties = {visible: true}, - // Update for current screen size, keeping overlay proportions constant. - screen = Controller.getViewportDimensions(), - screenRatio = screen.x / screen.y; - if (screenRatio < OVERLAY_RATIO) { - properties.width = screen.x; - properties.height = screen.x / OVERLAY_RATIO; - properties.x = 0; - properties.y = (screen.y - properties.height) / 2; + var properties = {visible: true}; + + if (HMD.active) { + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); + + lastOverlayPosition = HMD.getHUDLookAtPosition3D(); + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); } else { - properties.height = screen.y; - properties.width = screen.y * OVERLAY_RATIO; - properties.y = 0; - properties.x = (screen.x - properties.width) / 2; + // make sure HMD is hidden + Overlays.editOverlay(overlayHMD, { visible: false }); + + // Update for current screen size, keeping overlay proportions constant. + var screen = Controller.getViewportDimensions(); + + // keep the overlay it's natural size and always center it... + Overlays.editOverlay(overlay, { visible: true, + x: ((screen.x - OVERLAY_WIDTH) / 2), + y: ((screen.y - OVERLAY_HEIGHT) / 2) }); } - Overlays.editOverlay(overlay, properties); } function hideOverlay() { Overlays.editOverlay(overlay, {visible: false}); + Overlays.editOverlay(overlayHMD, {visible: false}); } hideOverlay(); +function maybeMoveOverlay() { + if (isAway) { + // if we switched from HMD to Desktop, make sure to hide our HUD overlay and show the + // desktop overlay + if (!HMD.active) { + showOverlay(); // this will also recenter appropriately + } + + if (HMD.active) { + // Note: instead of moving it directly to the lookAt, we will move it slightly toward the + // new look at. This will result in a more subtle slerp toward the look at and reduce jerkiness + var EASE_BY_RATIO = 0.1; + var lookAt = HMD.getHUDLookAtPosition3D(); + var lookAtChange = Vec3.subtract(lookAt, lastOverlayPosition); + var halfWayBetweenOldAndLookAt = Vec3.multiply(lookAtChange, EASE_BY_RATIO); + var newOverlayPosition = Vec3.sum(lastOverlayPosition, halfWayBetweenOldAndLookAt); + lastOverlayPosition = newOverlayPosition; + + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); + + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); + } + } +} // MAIN CONTROL var wasMuted, isAway; @@ -106,6 +168,12 @@ function goAway() { MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view playAwayAnimation(); // animation is still seen by others showOverlay(); + + // tell the Reticle, we want to stop capturing the mouse until we come back + Reticle.allowMouseCapture = false; + if (HMD.active) { + Reticle.visible = false; + } } function goActive() { if (!isAway) { @@ -119,20 +187,28 @@ function goActive() { MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting. stopAwayAnimation(); hideOverlay(); + + // tell the Reticle, we are ready to capture the mouse again and it should be visible + Reticle.allowMouseCapture = true; + Reticle.visible = true; + if (HMD.active) { + Reticle.position = HMD.getHUDLookAtPosition2D(); + } } function maybeGoActive(event) { if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it) return; } - if (!isAway && (event.text === '.')) { + if (!isAway && (event.text == 'ESC')) { goAway(); } else { goActive(); } } -var wasHmdActive = false; -var wasMouseCaptured = false; +var wasHmdActive = HMD.active; +var wasMouseCaptured = Reticle.mouseCaptured; + function maybeGoAway() { if (HMD.active !== wasHmdActive) { wasHmdActive = !wasHmdActive; @@ -141,10 +217,8 @@ function maybeGoAway() { } } - // If the mouse has gone from captured, to non-captured state, - // then it likely means the person is still in the HMD, but has - // tabbed away from the application (meaning they don't have mouse - // control) and they likely want to go into an away state + // If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD, but + // tabbed away from the application (meaning they don't have mouse control) and they likely want to go into an away state if (Reticle.mouseCaptured !== wasMouseCaptured) { wasMouseCaptured = !wasMouseCaptured; if (!wasMouseCaptured) { @@ -153,6 +227,8 @@ function maybeGoAway() { } } +Script.update.connect(maybeMoveOverlay); + Script.update.connect(maybeGoAway); Controller.mousePressEvent.connect(goActive); Controller.keyPressEvent.connect(maybeGoActive); diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 04e93334cb..964fca4136 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -1,3 +1,4 @@ +"use strict"; // handControllerGrab.js // // Created by Eric Levin on 9/2/15 @@ -801,6 +802,8 @@ function MyController(hand) { this.isInitialGrab = false; this.doubleParentGrab = false; + this.checkForStrayChildren(); + if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { this.setState(STATE_RELEASE); return; @@ -1046,9 +1049,14 @@ function MyController(hand) { } this.distanceHolding = function() { - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + + // controller pose is in avatar frame + var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + + // transform it into world frame + var controllerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); + var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var now = Date.now(); @@ -1056,12 +1064,10 @@ function MyController(hand) { this.currentObjectPosition = grabbedProperties.position; this.currentObjectRotation = grabbedProperties.rotation; this.currentObjectTime = now; - this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); - this.handPreviousRotation = handRotation; this.currentCameraOrientation = Camera.orientation; // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object - this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, controllerPosition) + 1.0); if (this.radiusScalar < 1.0) { this.radiusScalar = 1.0; } @@ -1091,10 +1097,10 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("startDistanceGrab"); } - this.currentAvatarPosition = MyAvatar.position; - this.currentAvatarOrientation = MyAvatar.orientation; - this.turnOffVisualizations(); + + this.previousControllerPosition = controllerPosition; + this.previousControllerRotation = controllerRotation; }; this.continueDistanceHolding = function() { @@ -1106,10 +1112,13 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); - var handPosition = this.getHandPosition(); - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + // controller pose is in avatar frame + var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + + // transform it into world frame + var controllerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); + var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); @@ -1122,66 +1131,27 @@ function MyController(hand) { return; } + var now = Date.now(); + this.currentObjectTime = now; // the action was set up on a previous call. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + var radius = Vec3.distance(this.currentObjectPosition, controllerPosition) * this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; if (radius < 1.0) { radius = 1.0; } - // how far did avatar move this timestep? - var currentPosition = MyAvatar.position; - var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); - this.currentAvatarPosition = currentPosition; + // scale delta controller hand movement by radius. + var handMoved = Vec3.multiply(Vec3.subtract(controllerPosition, this.previousControllerPosition), radius); - // How far did the avatar turn this timestep? - // Note: The following code is too long because we need a Quat.quatBetween() function - // that returns the minimum quaternion between two quaternions. - var currentOrientation = MyAvatar.orientation; - if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { - var negativeCurrentOrientation = { - x: -currentOrientation.x, - y: -currentOrientation.y, - z: -currentOrientation.z, - w: -currentOrientation.w - }; - var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } else { - var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } - var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); - var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); - var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); - var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); - this.currentAvatarOrientation = currentOrientation; + // double delta controller rotation + var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, + controllerRotation, + DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + Quat.inverse(this.previousControllerRotation)); - // how far did hand move this timestep? - var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); - this.handRelativePreviousPosition = handToAvatar; - - // magnify the hand movement but not the change from avatar movement & rotation - handMoved = Vec3.subtract(handMoved, handMovementFromTurning); - var superHandMoved = Vec3.multiply(handMoved, radius); - - // Move the object by the magnified amount and then by amount from avatar movement & rotation - var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); - newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); - newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); - - var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters - var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentObjectPosition = newObjectPosition; - this.currentObjectTime = now; - - // this doubles hand rotation - var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, - handRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.handPreviousRotation)); - this.handPreviousRotation = handRotation; + // update the currentObject position and rotation. + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); this.callEntityMethodOnGrabbed("continueDistantGrab"); @@ -1192,6 +1162,7 @@ function MyController(hand) { var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position); if (handControllerData.disableMoveWithHead !== true) { // mix in head motion if (MOVE_WITH_HEAD) { @@ -1231,6 +1202,7 @@ function MyController(hand) { } } + var handPosition = this.getHandPosition(); //visualizations if (USE_ENTITY_LINES_FOR_MOVING === true) { @@ -1262,6 +1234,9 @@ function MyController(hand) { } else { print("continueDistanceHolding -- updateAction failed"); } + + this.previousControllerPosition = controllerPosition; + this.previousControllerRotation = controllerRotation; }; this.setupHoldAction = function() { @@ -1444,6 +1419,13 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position"]); + if (!props.position) { + // server may have reset, taking our equipped entity with it. move back to "off" stte + this.setState(STATE_RELEASE); + this.callEntityMethodOnGrabbed("releaseGrab"); + return; + } + if (props.parentID == MyAvatar.sessionUUID && Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. @@ -1751,6 +1733,17 @@ function MyController(hand) { return data; }; + this.checkForStrayChildren = function() { + // sometimes things can get parented to a hand and this script is unaware. Search for such entities and + // unhook them. + var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); + children.forEach(function(childID) { + print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); + Entities.editEntity(childID, {parentID: NULL_UUID}); + }); + } + this.deactivateEntity = function(entityID, noVelocity) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); if (data && data["refCount"]) { diff --git a/examples/controllers/reticleHandRotationTest.js b/examples/controllers/handControllerMouse.js similarity index 74% rename from examples/controllers/reticleHandRotationTest.js rename to examples/controllers/handControllerMouse.js index 631486d1a3..e53fc34df1 100644 --- a/examples/controllers/reticleHandRotationTest.js +++ b/examples/controllers/handControllerMouse.js @@ -1,5 +1,5 @@ // -// reticleHandRotationTest.js +// handControllerMouse.js // examples/controllers // // Created by Brad Hefta-Gaub on 2015/12/15 @@ -10,6 +10,9 @@ // var DEBUGGING = false; +var angularVelocityTrailingAverage = 0.0; // Global trailing average used to decide whether to move reticle at all +var lastX = 0; +var lastY = 0; Math.clamp=function(a,b,c) { return Math.max(b,Math.min(c,a)); @@ -31,12 +34,17 @@ function moveReticleAbsolute(x, y) { var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation"; var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick); -mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick); +if (Controller.Hardware.Hydra !== undefined) { + mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick); + mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick); +} +if (Controller.Hardware.Vive !== undefined) { + mapping.from(Controller.Hardware.Vive.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); + mapping.from(Controller.Hardware.Vive.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); +} + mapping.enable(); - - function debugPrint(message) { if (DEBUGGING) { print(message); @@ -76,7 +84,7 @@ Script.update.connect(function(deltaTime) { } // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers - var VELOCITY_FILTER_GAIN = 1.0; + var VELOCITY_FILTER_GAIN = 0.5; filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0)); filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0)); var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias); @@ -96,11 +104,17 @@ Script.update.connect(function(deltaTime) { var y = screenSizeY * yRatio; // don't move the reticle with the hand controllers unless the controllers are actually being moved - var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.0001; + // take a time average of angular velocity, and don't move mouse at all if it's below threshold + + var AVERAGING_INTERVAL = 0.95; + var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03; var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias; + angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL); - if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityMagnitude > MINIMUM_CONTROLLER_ANGULAR_VELOCITY)) { + if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) { moveReticleAbsolute(x, y); + lastX = x; + lastY = y; } }); diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 35af5f4eae..2c024c5bf0 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -22,3 +22,4 @@ Script.load("grab.js"); Script.load("directory.js"); Script.load("dialTone.js"); Script.load("attachedEntitiesManager.js"); +Script.load("depthReticle.js"); diff --git a/examples/depthReticle.js b/examples/depthReticle.js index a60e61d07c..14215bff3d 100644 --- a/examples/depthReticle.js +++ b/examples/depthReticle.js @@ -5,6 +5,8 @@ // Copyright 2016 High Fidelity, Inc. // // When used in HMD, this script will make the reticle depth track to any clickable item in view. +// This script also handles auto-hiding the reticle after inactivity, as well as having the reticle +// seek the look at position upon waking up. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -12,17 +14,104 @@ var APPARENT_2D_OVERLAY_DEPTH = 1.0; var APPARENT_MAXIMUM_DEPTH = 100.0; // this is a depth at which things all seem sufficiently distant -var lastDepthCheckTime = 0; +var lastDepthCheckTime = Date.now(); +var desiredDepth = APPARENT_2D_OVERLAY_DEPTH; +var TIME_BETWEEN_DEPTH_CHECKS = 100; +var MINIMUM_DEPTH_ADJUST = 0.01; +var NON_LINEAR_DIVISOR = 2; +var MINIMUM_SEEK_DISTANCE = 0.01; -Script.update.connect(function(deltaTime) { - var TIME_BETWEEN_DEPTH_CHECKS = 100; - var timeSinceLastDepthCheck = Date.now() - lastDepthCheckTime; +var lastMouseMove = Date.now(); +var lastMouseX = Reticle.position.x; +var lastMouseY = Reticle.position.y; +var HIDE_STATIC_MOUSE_AFTER = 3000; // 3 seconds +var shouldSeekToLookAt = false; +var fastMouseMoves = 0; +var averageMouseVelocity = 0; +var WEIGHTING = 1/20; // simple moving average over last 20 samples +var ONE_MINUS_WEIGHTING = 1 - WEIGHTING; +var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50; + +Controller.mouseMoveEvent.connect(function(mouseEvent) { + var now = Date.now(); + + // if the reticle is hidden, and we're not in away mode... + if (!Reticle.visible && Reticle.allowMouseCapture) { + Reticle.visible = true; + if (HMD.active) { + shouldSeekToLookAt = true; + } + } else { + // even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it) + // then they are probably looking for it, and we should move into seekToLookAt mode + if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) { + var dx = Reticle.position.x - lastMouseX; + var dy = Reticle.position.y - lastMouseY; + var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move + var mouseMoveDistance = Math.sqrt((dx*dx) + (dy*dy)); + var mouseVelocity = mouseMoveDistance / dt; + averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * mouseVelocity); + if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) { + shouldSeekToLookAt = true; + } + } + } + lastMouseMove = now; + lastMouseX = mouseEvent.x; + lastMouseY = mouseEvent.y; +}); + +function seekToLookAt() { + // if we're currently seeking the lookAt move the mouse toward the lookat + if (shouldSeekToLookAt) { + averageMouseVelocity = 0; // reset this, these never count for movement... + var lookAt2D = HMD.getHUDLookAtPosition2D(); + var currentReticlePosition = Reticle.position; + var distanceBetweenX = lookAt2D.x - Reticle.position.x; + var distanceBetweenY = lookAt2D.y - Reticle.position.y; + var moveX = distanceBetweenX / NON_LINEAR_DIVISOR; + var moveY = distanceBetweenY / NON_LINEAR_DIVISOR; + var newPosition = { x: Reticle.position.x + moveX, y: Reticle.position.y + moveY }; + var closeEnoughX = false; + var closeEnoughY = false; + if (moveX < MINIMUM_SEEK_DISTANCE) { + newPosition.x = lookAt2D.x; + closeEnoughX = true; + } + if (moveY < MINIMUM_SEEK_DISTANCE) { + newPosition.y = lookAt2D.y; + closeEnoughY = true; + } + Reticle.position = newPosition; + if (closeEnoughX && closeEnoughY) { + shouldSeekToLookAt = false; + } + } +} + +function autoHideReticle() { + // if we haven't moved in a long period of time, and we're not pointing at some + // system overlay (like a window), then hide the reticle + if (Reticle.visible && !Reticle.pointingAtSystemOverlay) { + var now = Date.now(); + var timeSinceLastMouseMove = now - lastMouseMove; + if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) { + Reticle.visible = false; + } + } +} + +function checkReticleDepth() { + var now = Date.now(); + var timeSinceLastDepthCheck = now - lastDepthCheckTime; if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) { + var newDesiredDepth = desiredDepth; + lastDepthCheckTime = now; var reticlePosition = Reticle.position; // first check the 2D Overlays if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(reticlePosition)) { - Reticle.setDepth(APPARENT_2D_OVERLAY_DEPTH); + newDesiredDepth = APPARENT_2D_OVERLAY_DEPTH; } else { var pickRay = Camera.computePickRay(reticlePosition.x, reticlePosition.y); @@ -37,11 +126,42 @@ Script.update.connect(function(deltaTime) { // If either the overlays or entities intersect, then set the reticle depth to // the distance of intersection if (result.intersects) { - Reticle.setDepth(result.distance); + newDesiredDepth = result.distance; } else { // if nothing intersects... set the depth to some sufficiently large depth - Reticle.setDepth(APPARENT_MAXIMUM_DEPTH); + newDesiredDepth = APPARENT_MAXIMUM_DEPTH; } } + + // If the desired depth has changed, reset our fade start time + if (desiredDepth != newDesiredDepth) { + desiredDepth = newDesiredDepth; + } + } + +} + +function moveToDesiredDepth() { + // move the reticle toward the desired depth + if (desiredDepth != Reticle.depth) { + + // cut distance between desiredDepth and current depth in half until we're close enough + var distanceToAdjustThisCycle = (desiredDepth - Reticle.depth) / NON_LINEAR_DIVISOR; + if (Math.abs(distanceToAdjustThisCycle) < MINIMUM_DEPTH_ADJUST) { + newDepth = desiredDepth; + } else { + newDepth = Reticle.depth + distanceToAdjustThisCycle; + } + + Reticle.setDepth(newDepth); + } +} + +Script.update.connect(function(deltaTime) { + autoHideReticle(); // auto hide reticle for desktop or HMD mode + if (HMD.active) { + seekToLookAt(); // handle moving the reticle toward the look at + checkReticleDepth(); // make sure reticle is at correct depth + moveToDesiredDepth(); // move the fade the reticle to the desired depth } }); diff --git a/examples/edit.js b/examples/edit.js index 50a66ea31f..bc0665213e 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -89,6 +89,9 @@ var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode"; var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; + +// marketplace info, etc. not quite ready yet. +var SHOULD_SHOW_PROPERTY_MENU = false; var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain." var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain." @@ -711,13 +714,32 @@ var intersection; var SCALE_FACTOR = 200.0; -function rayPlaneIntersection(pickRay, point, normal) { +function rayPlaneIntersection(pickRay, point, normal) { // + // + // This version of the test returns the intersection of a line with a plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); - var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); } +function rayPlaneIntersection2(pickRay, point, normal) { + // + // This version of the test returns false if the ray is directed away from the plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + if (t < 0.0) { + return false; + } else { + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); + } +} + function findClickedEntity(event) { var pickZones = event.isControl; @@ -758,7 +780,8 @@ function findClickedEntity(event) { var foundEntity = result.entityID; return { pickRay: pickRay, - entityID: foundEntity + entityID: foundEntity, + intersection: result.intersection }; } @@ -926,6 +949,7 @@ function mouseReleaseEvent(event) { } function mouseClickEvent(event) { + var wantDebug = false; if (isActive && event.isLeftButton) { var result = findClickedEntity(event); if (result === null) { @@ -940,11 +964,15 @@ function mouseClickEvent(event) { var properties = Entities.getEntityProperties(foundEntity); if (isLocked(properties)) { - print("Model locked " + properties.id); + if (wantDebug) { + print("Model locked " + properties.id); + } } else { var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } // P P - Model // /| A - Palm // / | d B - unit vector toward tip @@ -981,8 +1009,9 @@ function mouseClickEvent(event) { } else { selectionManager.addEntity(foundEntity, true); } - - print("Model selected: " + foundEntity); + if (wantDebug) { + print("Model selected: " + foundEntity); + } selectionDisplay.select(selectedEntityID, event); if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { @@ -996,6 +1025,9 @@ function mouseClickEvent(event) { } else if (event.isRightButton) { var result = findClickedEntity(event); if (result) { + if (SHOULD_SHOW_PROPERTY_MENU !== true) { + return; + } var properties = Entities.getEntityProperties(result.entityID); if (properties.marketplaceID) { propertyMenu.marketplaceID = properties.marketplaceID; @@ -1869,9 +1901,11 @@ PopupMenu = function() { return this; }; + var propertyMenu = PopupMenu(); propertyMenu.onSelectMenuItem = function(name) { + if (propertyMenu.marketplaceID) { showMarketplace(propertyMenu.marketplaceID); } diff --git a/examples/entityScripts/lightningEntity.js b/examples/entityScripts/lightningEntity.js new file mode 100644 index 0000000000..40c5364a6a --- /dev/null +++ b/examples/entityScripts/lightningEntity.js @@ -0,0 +1,339 @@ +// +// lightningEntity.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 3/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// This is an example of an entity script which will randomly create a flash of lightning and a thunder sound +// effect, as well as a background rain sound effect. It can be applied to any entity, although it works best +// on a zone entity. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +(function () { + "use strict"; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Various configurable settings. + // + // You can change these values to change some of the various effects of the rain storm. + // These values can also be controlled by setting user properties on the entity that you've attached this script to. + // add a "lightning" section to a JSON encoded portion of the user data... for example: + // { + // "lightning": { + // "flashMax": 20, + // "flashMin": 0, + // "flashMaxRandomness": 10, + // "flashIntensityStepRandomeness": 2, + // "averageLighteningStrikeGap": 120, + // "extraRandomRangeLightningStrikeGap": 10, + // "thunderURL": "atp:1336efe995398f5e0d46b37585785de8ba872fe9a9b718264db03748cd41c758.wav", + // "thunderVolume": 0.1, + // "rainURL": "atp:e0cc7438aca776636f6e6f731685781d9999b961c945e4e5760d937be5beecdd.wav", + // "rainVolume": 0.05 + // } + // // NOTE: you can have other user data here as well, so long as it's JSON encoded, it won't impact the lightning script + // } + // + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + + var MAX_FLASH_INTENSITY = 20; // this controls how bright the lightning effect appears + var MIN_FLASH_INTENSITY = 0; // this is probably best at 0, but it could be higher, which will make the lightning not fade completely to darkness before going away. + var MAX_FLASH_INTENSITY_RANDOMNESS = 10; // this will add some randomness to the max brightness of the lightning + var FLASH_INTENSITY_STEP_RANDOMNESS = 2; // as the lightning goes from min to max back to min, this will make it more random in it's brightness + var AVERAGE_LIGHTNING_STRIKE_GAP_IN_SECONDS = 120; // how long on average between lighting + var EXTRA_RANDOM_RANGE_LIGHTNING_STRIKE_GAP_IN_SECONDS = 10; // some randomness to the lightning gap + var THUNDER_SOUND_URL = "https://s3.amazonaws.com/hifi-public/brad/rainstorm/thunder-48k.wav"; // thunder sound effect, must be 48k 16bit PCM + var THUNDER_VOLUME = 1; // adjust the volume of the thunder sound effect + var RAIN_SOUND_URL = "https://s3.amazonaws.com/hifi-public/brad/rainstorm/rain.wav"; // the background rain, this will loop + var RAIN_VOLUME = 0.05; // adjust the volume of the rain sound effect. + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Various constants and variables we need access to in all scopes + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + + var MSECS_PER_SECOND = 1000; // number of milliseconds in a second, a universal truth, don't change this. :) + + // This is a little trick for implementing entity scripts. Many of the entity callbacks will have the JavaScript + // "this" variable set, but if you connect to a signal like update, "this" won't correctly point to the instance + // of your entity script, and so we set "_this" and use it in cases we need to access "this". We need to define + // the variable in this scope so that it's not share among all entities. + + var _this; // this is important here... or else the _this will be globally scoped. + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Various helper functions... + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Helper function for returning either a value, or the default value if the value is undefined. This is + // is handing in parsing JSON where you don't know if the values have been set or not. + function valueOrDefault(value, defaultValue) { + if (value !== undefined) { + return value; + } + return defaultValue; + } + + // return a random float between high and low + function randFloat(low, high) { + return low + Math.random() * (high - low); + } + + // the "constructor" for our class. pretty simple, it just sets our _this, so we can access it later. + function Lightning() { + _this = this; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // The class prototype + // + // This is the class definition/prototype for our class. Funtions declared here will be accessible + // via the instance of the entity + // + Lightning.prototype = { + + // preload() + // This is called by every viewer/interface instance that "sees" the entity you've attached this script to. + // If this is a zone entity, then it will surely be called whenever someone enters the zone. + preload: function (entityID) { + + // we always make a point of remember our entityID, so that we can access our entity later + _this.entityID = entityID; + + // set up some of our time related state + var now = Date.now(); + _this.lastUpdate = now; + _this.lastStrike = now; + + // some of our other state related items + _this.lightningID = false; // this will be the entityID for any lightning that we create + _this.lightningActive = false; // are we actively managing lightning + + // Get the entities userData property, to see if someone has overridden any of our default settings + var userDataText = Entities.getEntityProperties(entityID, ["userData"]).userData; + var userData = {}; + if (userDataText !== "") { + userData = JSON.parse(userDataText); + } + var lightningUserData = valueOrDefault(userData.lightning, {}); + _this.flashIntensityStepRandomeness = valueOrDefault(lightningUserData.flashIntensityStepRandomness, FLASH_INTENSITY_STEP_RANDOMNESS); + _this.flashMax = valueOrDefault(lightningUserData.flashMax, MAX_FLASH_INTENSITY); + _this.flashMin = valueOrDefault(lightningUserData.flashMin, MIN_FLASH_INTENSITY); + _this.flashMaxRandomness = valueOrDefault(lightningUserData.flashMaxRandomness, MAX_FLASH_INTENSITY_RANDOMNESS); + _this.averageLightningStrikeGap = valueOrDefault(lightningUserData.averageLightningStrikeGap, AVERAGE_LIGHTNING_STRIKE_GAP_IN_SECONDS); + _this.extraRandomRangeLightningStrikeGap = valueOrDefault(lightningUserData.extraRandomRangeLightningStrikeGap, EXTRA_RANDOM_RANGE_LIGHTNING_STRIKE_GAP_IN_SECONDS); + + var thunderURL = valueOrDefault(lightningUserData.thunderURL, THUNDER_SOUND_URL); + _this.thunderSound = SoundCache.getSound(thunderURL); // start downloading the thunder into the cache in case we need it later + _this.thunderVolume = valueOrDefault(lightningUserData.thunderVolume, THUNDER_VOLUME); + + var rainURL = valueOrDefault(lightningUserData.rainURL, RAIN_SOUND_URL); + _this.rainSound = SoundCache.getSound(rainURL); // start downloading the rain, we will be using it for sure + _this.rainVolume = valueOrDefault(lightningUserData.rainVolume, RAIN_VOLUME); + _this.rainPlaying = false; + + Script.update.connect(_this.onUpdate); // connect our onUpdate to a regular update signal from the interface + }, + + // unload() + // This is called by every viewer/interface instance that "sees" the entity when you "stop knowing about" the + // entity. Usually this means the user has left then domain, or maybe the entity has been deleted. We need to + // clean up anything transient that we created here. In this case it means disconnecting from the update signal + // and stopping the rain sound if we started it. + unload: function ( /*entityID*/ ) { + Script.update.disconnect(_this.onUpdate); + if (_this.rainInjector !== undefined) { + _this.rainInjector.stop(); + } + }, + + // plays the rain sound effect. This is played locally, which means it doesn't provide spatialization, which + // we don't really want for the ambient sound effect of rain. Also, since it's playing locally, we don't send + // the stream to the audio mixer. Another subtle side effect of playing locally is that the sound isn't in sync + // for all users in the domain, but that's ok for a sound effect like rain which is more of a white noise like + // sound effect. + playLocalRain: function () { + var myPosition = Entities.getEntityProperties(_this.entityID, "position").position; + _this.rainInjector = Audio.playSound(_this.rainSound, { + position: myPosition, + volume: _this.rainVolume, + loop: true, + localOnly: true + }); + _this.rainPlaying = true; + }, + + // this handles a single "step" of the lightning flash effect. It assumes a light entity has already been created, + // and all it really does is change the intensity of the light based on the settings that are part of this entity's + // userData. + flashLightning: function (lightningID) { + var lightningProperties = Entities.getEntityProperties(lightningID, ["userData", "intensity"]); + var lightningParameters = JSON.parse(lightningProperties.userData); + var currentIntensity = lightningProperties.intensity; + var flashDirection = lightningParameters.flashDirection; + var flashMax = lightningParameters.flashMax; + var flashMin = lightningParameters.flashMin; + var flashIntensityStepRandomeness = lightningParameters.flashIntensityStepRandomeness; + var newIntensity = currentIntensity + flashDirection + randFloat(-flashIntensityStepRandomeness, flashIntensityStepRandomeness); + + if (flashDirection > 0) { + if (newIntensity >= flashMax) { + flashDirection = -1; // reverse flash + newIntensity = flashMax; + } + } else { + if (newIntensity <= flashMin) { + flashDirection = 1; // reverse flash + newIntensity = flashMin; + } + } + + // if we reached 0 intensity, then we're done with this strike... + if (newIntensity === 0) { + _this.lightningActive = false; + Entities.deleteEntity(lightningID); + } + + // FIXME - we probably don't need to re-edit the userData of the light... we're only + // changing direction, the rest are the same... we could just store direction in our + // own local variable state + var newLightningParameters = JSON.stringify({ + flashDirection: flashDirection, + flashIntensityStepRandomeness: flashIntensityStepRandomeness, + flashMax: flashMax, + flashMin: flashMin + }); + + // this is what actually creates the effect, changing the intensity of the light + Entities.editEntity(lightningID, {intensity: newIntensity, userData: newLightningParameters}); + }, + + // findMyLightning() is designed to make the script more robust. Since we're an open editable platform + // it's possible that from the time that we started the lightning effect until "now" when we're attempting + // to change the light, some other client might have deleted our light. Before we proceed in editing + // the light, we check to see if it exists. + findMyLightning: function () { + if (_this.lightningID !== false) { + var lightningName = Entities.getEntityProperties(_this.lightningID, "name").name; + if (lightningName !== undefined) { + return _this.lightningID; + } + } + return false; + }, + + // findOtherLightning() is designed to allow this script to work in a "multi-user" environment, which we + // must assume we are in. Since every user/viewer/client that connect to the domain and "sees" our entity + // is going to run this script, any of them could be in charge of flashing the lightning. So before we + // start to flash the lightning, we will first check to see if someone else already is. + // + // returns true if some other lightning exists... likely because some other viewer is flashing it + // returns false if no other lightning exists... + findOtherLightning: function () { + var myPosition = Entities.getEntityProperties(_this.entityID, "position").position; + + // find all entities near me... + var entities = Entities.findEntities(myPosition, 1000); + var checkEntityID; + var checkProperties; + var entity; + for (entity = 0; entity < entities.length; entity++) { + checkEntityID = entities[entity]; + checkProperties = Entities.getEntityProperties(checkEntityID, ["name", "type"]); + + // check to see if they are lightning + if (checkProperties.type === "Light" && checkProperties.name === "lightning for creator:" + _this.entityID) { + return true; + } + } + return false; + }, + + // createNewLightning() actually creates new lightning and plays the thunder sound + createNewLightning: function () { + var myPosition = Entities.getEntityProperties(_this.entityID, "position").position; + _this.lightningID = Entities.addEntity({ + type: "Light", + name: "lightning for creator:" + _this.entityID, + userData: JSON.stringify({ + flashDirection: 1, + flashIntensityStepRandomeness: _this.flashIntensityStepRandomeness, + flashMax: _this.flashMax + randFloat(-_this.flashMaxRandomness, _this.flashMaxRandomness), + flashMin: _this.flashMin + }), + falloffRadius: 1000, + intensity: 0, + position: myPosition, + collisionless: true, + dimensions: {x: 1000, y: 1000, z: 1000}, + color: {red: 255, green: 255, blue: 255}, + lifetime: 10 // lightning only lasts 10 seconds.... + }); + + // play the thunder... + Audio.playSound(_this.thunderSound, { + position: myPosition, + volume: _this.thunderVolume + }); + + return _this.lightningID; + }, + + // onUpdate() this will be called regularly, approximately every frame of the simulation. We will use + // it to determine if we need to do a lightning/thunder strike + onUpdate: function () { + var now = Date.now(); + + // check to see if rain is downloaded and not yet playing, if so start playing + if (!_this.rainPlaying && _this.rainSound.downloaded) { + _this.playLocalRain(); + } + + // NOTE: _this.lightningActive will only be TRUE if we are the one who created + // the lightning and we are in charge of flashing it... + if (_this.lightningActive) { + var lightningID = _this.findMyLightning(); + // if for some reason our lightning is gone... then just return to non-active state + if (lightningID === false) { + _this.lightningActive = false; + _this.lightningID = false; + } else { + // otherwise, flash our lightning... + _this.flashLightning(lightningID); + } + } else { + // whether or not it's time for us to strike, we always keep an eye out for anyone else + // striking... and if we see someone else striking, we will reset our lastStrike time + if (_this.findOtherLightning()) { + _this.lastStrike = now; + } + + var sinceLastStrike = now - _this.lastStrike; + var nextRandomStrikeTime = _this.averageLightningStrikeGap + + randFloat(-_this.extraRandomRangeLightningStrikeGap, + _this.extraRandomRangeLightningStrikeGap); + + if (sinceLastStrike > nextRandomStrikeTime * MSECS_PER_SECOND) { + + // so it's time for a strike... let's see if someone else has lightning... + // if no one else is flashing lightning... then we create it... + if (_this.findOtherLightning()) { + _this.lightningActive = false; + _this.lightningID = false; + } else { + _this.createNewLightning(); + _this.lightningActive = true; + } + + _this.lastStrike = now; + } + } + } + }; + + return new Lightning(); +}); \ No newline at end of file diff --git a/examples/gridTest.js b/examples/gridTest.js index 0d6040470f..5bbd3246ae 100644 --- a/examples/gridTest.js +++ b/examples/gridTest.js @@ -17,8 +17,8 @@ var SIZE = 10.0; var SEPARATION = 20.0; var ROWS_X = 30; var ROWS_Z = 30; -var TYPE = "Sphere"; // Right now this can be "Box" or "Model" or "Sphere" -var MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/props/LowPolyIsland/CypressTreeGroup.fbx"; +var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere" +var MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Instances/vesicle.fbx"; var MODEL_DIMENSION = { x: 33, y: 16, z: 49 }; var RATE_PER_SECOND = 1000; // The entity server will drop data if we create things too fast. var SCRIPT_INTERVAL = 100; @@ -38,6 +38,7 @@ Script.setInterval(function () { var numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0); for (var i = 0; i < numToCreate; i++) { var position = { x: SIZE + (x * SEPARATION), y: SIZE, z: SIZE + (z * SEPARATION) }; + print('position:'+JSON.stringify(position)) if (TYPE == "Model") { Entities.addEntity({ type: TYPE, diff --git a/examples/homeContent/whiteboardV2/eraserEntityScript.js b/examples/homeContent/whiteboardV2/eraserEntityScript.js new file mode 100644 index 0000000000..9306c7a5ab --- /dev/null +++ b/examples/homeContent/whiteboardV2/eraserEntityScript.js @@ -0,0 +1,109 @@ +// +// eraserEntityScript.js +// examples/homeContent/eraserEntityScript +// +// Created by Eric Levin on 2/17/15. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script provides logic for an object with attached script to erase nearby marker strokes +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + + +(function() { + Script.include("../../libraries/utils.js"); + var TRIGGER_CONTROLS = [ + Controller.Standard.LT, + Controller.Standard.RT, + ]; + var _this; + Eraser = function() { + _this = this; + + _this.ERASER_TRIGGER_THRESHOLD = 0.2; + _this.STROKE_NAME = "hifi-marker-stroke"; + _this.ERASER_TO_STROKE_SEARCH_RADIUS = 0.7; + _this.ERASER_RESET_WAIT_TIME = 3000; + }; + + Eraser.prototype = { + + startEquip: function(id, params) { + _this.equipped = true; + _this.hand = params[0] == "left" ? 0 : 1; + // We really only need to grab position of marker strokes once, and then just check to see if eraser comes near enough to those strokes + Overlays.editOverlay(_this.searchSphere, { + visible: true + }); + }, + continueEquip: function() { + _this.eraserPosition = Entities.getEntityProperties(_this.entityID, "position").position; + Overlays.editOverlay(_this.searchSphere, { + position: _this.eraserPosition + }); + this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[_this.hand]); + if (_this.triggerValue > _this.ERASER_TRIGGER_THRESHOLD) { + _this.continueHolding(); + } + }, + + + continueHolding: function() { + var strokeIDs = Entities.findEntities(_this.eraserPosition, _this.ERASER_TO_STROKE_SEARCH_RADIUS); + // Create a map of stroke entities and their positions + + strokeIDs.forEach(function(strokeID) { + var strokeProps = Entities.getEntityProperties(strokeID, ["position", "name"]); + if (strokeProps.name === _this.STROKE_NAME && Vec3.distance(_this.eraserPosition, strokeProps.position) < _this.ERASER_TO_STROKE_SEARCH_RADIUS) { + Entities.deleteEntity(strokeID); + } + }); + }, + + releaseEquip: function() { + Overlays.editOverlay(_this.searchSphere, { + visible: false + }); + + // Once user releases eraser, wait a bit then put marker back to its original position and rotation + Script.setTimeout(function() { + var userData = getEntityUserData(_this.entityID); + Entities.editEntity(_this.entityID, { + position: userData.originalPosition, + rotation: userData.originalRotation, + velocity: { + x: 0, + y: -0.01, + z: 0 + } + }); + }, _this.ERASER_RESET_WAIT_TIME); + }, + + + + preload: function(entityID) { + _this.entityID = entityID; + _this.searchSphere = Overlays.addOverlay('sphere', { + size: _this.ERASER_TO_STROKE_SEARCH_RADIUS, + color: { + red: 200, + green: 10, + blue: 10 + }, + alpha: 0.2, + solid: true, + visible: false + }) + + }, + + unload: function() { + Overlays.deleteOverlay(_this.searchSphere); + } + }; + + // entity scripts always need to return a newly constructed object of our type + return new Eraser(); +}); \ No newline at end of file diff --git a/examples/homeContent/whiteboardV2/markerEntityScript.js b/examples/homeContent/whiteboardV2/markerEntityScript.js new file mode 100644 index 0000000000..19907b283c --- /dev/null +++ b/examples/homeContent/whiteboardV2/markerEntityScript.js @@ -0,0 +1,219 @@ +// +// markerTipEntityScript.js +// examples/homeContent/markerTipEntityScript +// +// Created by Eric Levin on 2/17/15. +// Copyright 2016 High Fidelity, Inc. +// +// This script provides the logic for an object to draw marker strokes on its associated whiteboard + +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + + +(function() { + Script.include("../../libraries/utils.js"); + var TRIGGER_CONTROLS = [ + Controller.Standard.LT, + Controller.Standard.RT, + ]; + var MAX_POINTS_PER_STROKE = 40; + var _this; + MarkerTip = function() { + _this = this; + _this.MARKER_TEXTURE_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/textures/markerStroke.png"; + _this.strokeForwardOffset = 0.0001; + _this.STROKE_WIDTH_RANGE = { + min: 0.002, + max: 0.01 + }; + _this.MAX_MARKER_TO_BOARD_DISTANCE = 1.4; + _this.MIN_DISTANCE_BETWEEN_POINTS = 0.002; + _this.MAX_DISTANCE_BETWEEN_POINTS = 0.1; + _this.strokes = []; + _this.PAINTING_TRIGGER_THRESHOLD = 0.2; + _this.STROKE_NAME = "hifi-marker-stroke"; + _this.WHITEBOARD_SURFACE_NAME = "hifi-whiteboardDrawingSurface"; + _this.MARKER_RESET_WAIT_TIME = 3000; + }; + + MarkerTip.prototype = { + + startEquip: function(id, params) { + _this.whiteboards = []; + _this.equipped = true; + _this.hand = params[0] == "left" ? 0 : 1; + _this.markerColor = getEntityUserData(_this.entityID).markerColor; + // search for whiteboards + var markerPosition = Entities.getEntityProperties(_this.entityID, "position").position; + var entities = Entities.findEntities(markerPosition, 10); + entities.forEach(function(entity) { + var entityName = Entities.getEntityProperties(entity, "name").name; + if (entityName === _this.WHITEBOARD_SURFACE_NAME) { + + _this.whiteboards.push(entity); + } + }); + + print("intersectable entities " + JSON.stringify(_this.whiteboards)) + }, + + releaseEquip: function() { + _this.resetStroke(); + Overlays.editOverlay(_this.laserPointer, { + visible: false + }); + + // Once user releases marker, wait a bit then put marker back to its original position and rotation + Script.setTimeout(function() { + var userData = getEntityUserData(_this.entityID); + Entities.editEntity(_this.entityID, { + position: userData.originalPosition, + rotation: userData.originalRotation, + velocity: { + x: 0, + y: -0.01, + z: 0 + } + }); + }, _this.MARKER_RESET_WAIT_TIME); + }, + + + continueEquip: function() { + // cast a ray from marker and see if it hits anything + var markerProps = Entities.getEntityProperties(_this.entityID, ["position", "rotation"]); + + var pickRay = { + origin: markerProps.position, + direction: Quat.getFront(markerProps.rotation) + } + var intersection = Entities.findRayIntersectionBlocking(pickRay, true, _this.whiteboards); + + if (intersection.intersects && Vec3.distance(intersection.intersection, markerProps.position) < _this.MAX_MARKER_TO_BOARD_DISTANCE) { + _this.currentWhiteboard = intersection.entityID; + var whiteboardRotation = Entities.getEntityProperties(_this.currentWhiteboard, "rotation").rotation; + _this.whiteboardNormal = Quat.getFront(whiteboardRotation); + Overlays.editOverlay(_this.laserPointer, { + visible: true, + position: intersection.intersection, + rotation: whiteboardRotation + }) + _this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[_this.hand]); + if (_this.triggerValue > _this.PAINTING_TRIGGER_THRESHOLD) { + _this.paint(intersection.intersection) + } else { + _this.resetStroke(); + } + } else { + if (_this.currentStroke) { + _this.resetStroke(); + } + + Overlays.editOverlay(_this.laserPointer, { + visible: false + }); + } + + }, + + newStroke: function(position) { + _this.strokeBasePosition = position; + _this.currentStroke = Entities.addEntity({ + type: "PolyLine", + name: _this.STROKE_NAME, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + position: position, + textures: _this.MARKER_TEXTURE_URL, + color: _this.markerColor, + lifetime: 5000, + }); + + _this.linePoints = []; + _this.normals = []; + _this.strokes.push(_this.currentStroke); + }, + + paint: function(position) { + var basePosition = position; + if (!_this.currentStroke) { + if (_this.oldPosition) { + basePosition = _this.oldPosition; + } + _this.newStroke(basePosition); + } + + var localPoint = Vec3.subtract(basePosition, _this.strokeBasePosition); + localPoint = Vec3.sum(localPoint, Vec3.multiply(_this.whiteboardNormal, _this.strokeForwardOffset)); + + if (_this.linePoints.length > 0) { + var distance = Vec3.distance(localPoint, _this.linePoints[_this.linePoints.length - 1]); + if (distance < _this.MIN_DISTANCE_BETWEEN_POINTS) { + return; + } + } + _this.linePoints.push(localPoint); + _this.normals.push(_this.whiteboardNormal); + + var strokeWidths = []; + for (var i = 0; i < _this.linePoints.length; i++) { + // Create a temp array of stroke widths for calligraphy effect - start and end should be less wide + var pointsFromCenter = Math.abs(_this.linePoints.length / 2 - i); + var pointWidth = map(pointsFromCenter, 0, this.linePoints.length / 2, _this.STROKE_WIDTH_RANGE.max, this.STROKE_WIDTH_RANGE.min); + strokeWidths.push(pointWidth); + } + + Entities.editEntity(_this.currentStroke, { + linePoints: _this.linePoints, + normals: _this.normals, + strokeWidths: strokeWidths + }); + + if (_this.linePoints.length > MAX_POINTS_PER_STROKE) { + Entities.editEntity(_this.currentStroke, { + parentID: _this.currentWhiteboard + }); + _this.currentStroke = null; + _this.oldPosition = position; + } + }, + resetStroke: function() { + + Entities.editEntity(_this.currentStroke, { + parentID: _this.currentWhiteboard + }); + _this.currentStroke = null; + + _this.oldPosition = null; + }, + + preload: function(entityID) { + this.entityID = entityID; + _this.laserPointer = Overlays.addOverlay("circle3d", { + color: { + red: 220, + green: 35, + blue: 53 + }, + solid: true, + size: 0.01, + }); + + }, + + unload: function() { + Overlays.deleteOverlay(_this.laserPointer); + _this.strokes.forEach(function(stroke) { + Entities.deleteEntity(stroke); + }); + } + }; + + // entity scripts always need to return a newly constructed object of our type + return new MarkerTip(); +}); diff --git a/examples/homeContent/whiteboardV2/whiteboardSpawner.js b/examples/homeContent/whiteboardV2/whiteboardSpawner.js new file mode 100644 index 0000000000..32c207499a --- /dev/null +++ b/examples/homeContent/whiteboardV2/whiteboardSpawner.js @@ -0,0 +1,254 @@ +// +// whiteboardSpawner.js +// examples/homeContent/whiteboardV2 +// +// Created by Eric Levina on 2/17/16 +// Copyright 2016 High Fidelity, Inc. +// +// Run this script to spawn a whiteboard, markers, and an eraser. +// To draw on the whiteboard, equip a marker and hold down trigger with marker tip pointed at whiteboard +// +// 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 orientation = MyAvatar.orientation; +orientation = Quat.safeEulerAngles(orientation); +var markerRotation = Quat.fromVec3Degrees({ + x: orientation.x + 10, + y: orientation.y - 90, + z: orientation.z +}) +orientation.x = 0; +var whiteboardRotation = Quat.fromVec3Degrees({ + x: 0, + y: orientation.y, + z: 0 +}); +orientation = Quat.fromVec3Degrees(orientation); +var markers = []; + + +var whiteboardPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(orientation))); +var WHITEBOARD_MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Whiteboard-4.fbx"; +var WHITEBOARD_COLLISION_HULL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/whiteboardCollisionHull.obj"; +var whiteboard = Entities.addEntity({ + type: "Model", + name: "whiteboard", + modelURL: WHITEBOARD_MODEL_URL, + position: whiteboardPosition, + rotation: whiteboardRotation, + shapeType: 'compound', + compoundShapeURL: WHITEBOARD_COLLISION_HULL_URL, + dimensions: { + x: 1.86, + y: 2.7, + z: 0.4636 + }, +}); + +var whiteboardSurfacePosition = Vec3.sum(whiteboardPosition, { + x: 0.0, + y: 0.45, + z: 0.0 +}); +whiteboardSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(-0.02, Quat.getRight(whiteboardRotation))); +var moveForwardDistance = 0.02; +whiteboardFrontSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(-moveForwardDistance, Quat.getFront(whiteboardRotation))); +var whiteboardSurfaceSettings = { + type: "Box", + name: "hifi-whiteboardDrawingSurface", + dimensions: { + x: 1.82, + y: 1.8, + z: 0.01 + }, + color: { + red: 200, + green: 10, + blue: 200 + }, + position: whiteboardFrontSurfacePosition, + rotation: whiteboardRotation, + visible: false, + parentID: whiteboard +} +var whiteboardFrontDrawingSurface = Entities.addEntity(whiteboardSurfaceSettings); + + +whiteboardBackSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(moveForwardDistance, Quat.getFront(whiteboardRotation))); +whiteboardSurfaceSettings.position = whiteboardBackSurfacePosition; + +var whiteboardBackDrawingSurface = Entities.addEntity(whiteboardSurfaceSettings); + + +var WHITEBOARD_RACK_DEPTH = 1.9; + +var ERASER_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/eraser-2.fbx"; +var ERASER_SCRIPT_URL = Script.resolvePath("eraserEntityScript.js?v43"); +var eraserPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(WHITEBOARD_RACK_DEPTH, Quat.getFront(whiteboardRotation))); +eraserPosition = Vec3.sum(eraserPosition, Vec3.multiply(-0.5, Quat.getRight(whiteboardRotation))); +var eraserRotation = markerRotation; + +var eraser = Entities.addEntity({ + type: "Model", + modelURL: ERASER_MODEL_URL, + position: eraserPosition, + script: ERASER_SCRIPT_URL, + shapeType: "box", + dimensions: { + x: 0.0858, + y: 0.0393, + z: 0.2083 + }, + rotation: eraserRotation, + dynamic: true, + gravity: { + x: 0, + y: -1, + z: 0 + }, + velocity: { + x: 0, + y: -0.1, + z: 0 + }, + userData: JSON.stringify({ + originalPosition: eraserPosition, + originalRotation: eraserRotation, + wearable: { + joints: { + RightHand: [{ + x: 0.020, + y: 0.120, + z: 0.049 + }, { + x: 0.1004, + y: 0.6424, + z: 0.717, + w: 0.250 + }], + LeftHand: [{ + x: -0.005, + y: 0.1101, + z: 0.053 + }, { + x: 0.723, + y: 0.289, + z: 0.142, + w: 0.610 + }] + } + } + }) +}); + +createMarkers(); + +function createMarkers() { + var modelURLS = [ + "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-blue.fbx", + "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-red.fbx", + "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-black.fbx", + ]; + + var markerPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(WHITEBOARD_RACK_DEPTH, Quat.getFront(orientation))); + + createMarker(modelURLS[0], markerPosition, { + red: 10, + green: 10, + blue: 200 + }); + + markerPosition = Vec3.sum(markerPosition, Vec3.multiply(-0.2, Quat.getFront(markerRotation))); + createMarker(modelURLS[1], markerPosition, { + red: 200, + green: 10, + blue: 10 + }); + + markerPosition = Vec3.sum(markerPosition, Vec3.multiply(0.4, Quat.getFront(markerRotation))); + createMarker(modelURLS[2], markerPosition, { + red: 10, + green: 10, + blue: 10 + }); +} + + +function createMarker(modelURL, markerPosition, markerColor) { + var MARKER_SCRIPT_URL = Script.resolvePath("markerEntityScript.js?v1" + Math.random()); + var marker = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + rotation: markerRotation, + shapeType: "box", + name: "marker", + dynamic: true, + gravity: { + x: 0, + y: -1, + z: 0 + }, + velocity: { + x: 0, + y: -0.1, + z: 0 + }, + position: markerPosition, + dimensions: { + x: 0.027, + y: 0.027, + z: 0.164 + }, + name: "marker", + script: MARKER_SCRIPT_URL, + userData: JSON.stringify({ + originalPosition: markerPosition, + originalRotation: markerRotation, + markerColor: markerColor, + wearable: { + joints: { + RightHand: [{ + x: 0.001, + y: 0.139, + z: 0.050 + }, { + x: -0.73, + y: -0.043, + z: -0.108, + w: -0.666 + }], + LeftHand: [{ + x: 0.007, + y: 0.151, + z: 0.061 + }, { + x: -0.417, + y: 0.631, + z: -0.389, + w: -0.525 + }] + } + } + }) + }); + + markers.push(marker); + +} + + + +function cleanup() { + Entities.deleteEntity(whiteboard); + Entities.deleteEntity(whiteboardFrontDrawingSurface); + Entities.deleteEntity(whiteboardBackDrawingSurface); + Entities.deleteEntity(eraser); + markers.forEach(function(marker) { + Entities.deleteEntity(marker); + }); +} + +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index c1675ef044..95e695aa9c 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -16,6 +16,11 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; SPACE_LOCAL = "local"; SPACE_WORLD = "world"; +function objectTranslationPlanePoint(position, dimensions) { + var newPosition = { x: position.x, y: position.y, z: position.z }; + newPosition.y -= dimensions.y / 2.0; + return newPosition; +} SelectionManager = (function() { var that = {}; @@ -2252,15 +2257,20 @@ SelectionDisplay = (function() { var constrainMajorOnly = false; var startPosition = null; var duplicatedEntityIDs = null; + var translateXZTool = { mode: 'TRANSLATE_XZ', + pickPlanePosition: { x: 0, y: 0, z: 0 }, + greatestDimension: 0.0, + startingDistance: 0.0, + startingElevation: 0.0, onBegin: function(event) { SelectionManager.saveProperties(); startPosition = SelectionManager.worldPosition; var dimensions = SelectionManager.worldDimensions; var pickRay = Camera.computePickRay(event.x, event.y); - initialXZPick = rayPlaneIntersection(pickRay, startPosition, { + initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, z: 0 @@ -2297,16 +2307,56 @@ SelectionDisplay = (function() { visible: false }); }, + elevation: function(origin, intersection) { + return (origin.y - intersection.y) / Vec3.distance(origin, intersection); + }, onMove: function(event) { + var wantDebug = false; pickRay = Camera.computePickRay(event.x, event.y); - var pick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, { + var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, z: 0 }); + + // If the pick ray doesn't hit the pick plane in this direction, do nothing. + // this will happen when someone drags across the horizon from the side they started on. + if (!pick) { + if (wantDebug) { + print("Pick ray does not intersect XZ plane."); + } + return; + } + var vector = Vec3.subtract(pick, initialXZPick); + // If the mouse is too close to the horizon of the pick plane, stop moving + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var elevation = translateXZTool.elevation(pickRay.origin, pick); + if (wantDebug) { + print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); + } + if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || + (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { + if (wantDebug) { + print("too close to horizon!"); + } + return; + } + + // If the angular size of the object is too small, stop moving + var MIN_ANGULAR_SIZE = 0.01; // Radians + if (translateXZTool.greatestDimension > 0) { + var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); + if (wantDebug) { + print("Angular size = " + angularSize); + } + if (angularSize < MIN_ANGULAR_SIZE) { + return; + } + } + // If shifted, constrain to one axis if (event.isShifted) { if (Math.abs(vector.x) > Math.abs(vector.z)) { @@ -2368,7 +2418,7 @@ SelectionDisplay = (function() { grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); - var wantDebug = false; + for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; @@ -2645,11 +2695,6 @@ SelectionDisplay = (function() { pickRayPosition, planeNormal); - // Overlays.editOverlay(normalLine, { - // start: initialPosition, - // end: Vec3.sum(Vec3.multiply(100000, planeNormal), initialPosition), - // }); - SelectionManager.saveProperties(); }; @@ -3751,7 +3796,7 @@ SelectionDisplay = (function() { }; that.mousePressEvent = function(event) { - + var wantDebug = false; if (!event.isLeftButton) { // if another mouse button than left is pressed ignore it return false; @@ -3777,7 +3822,7 @@ SelectionDisplay = (function() { if (result.intersects) { - var wantDebug = false; + if (wantDebug) { print("something intersects... "); print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); @@ -3874,7 +3919,10 @@ SelectionDisplay = (function() { if (!somethingClicked) { - print("rotate handle case..."); + if (wantDebug) { + print("rotate handle case..."); + } + // After testing our stretch handles, then check out rotate handles Overlays.editOverlay(yawHandle, { @@ -3942,15 +3990,17 @@ SelectionDisplay = (function() { break; default: - print("mousePressEvent()...... " + overlayNames[result.overlayID]); + if (wantDebug) { + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + } mode = "UNKNOWN"; break; } } - - print(" somethingClicked:" + somethingClicked); - print(" mode:" + mode); - + if (wantDebug) { + print(" somethingClicked:" + somethingClicked); + print(" mode:" + mode); + } if (somethingClicked) { @@ -4093,12 +4143,25 @@ SelectionDisplay = (function() { switch (result.overlayID) { case selectionBox: activeTool = translateXZTool; + translateXZTool.pickPlanePosition = result.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), + SelectionManager.worldDimensions.z); + if (wantDebug) { + print("longest dimension: " + translateXZTool.greatestDimension); + translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + print("starting distance: " + translateXZTool.startingDistance); + translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); + print(" starting elevation: " + translateXZTool.startingElevation); + } + mode = translateXZTool.mode; activeTool.onBegin(event); somethingClicked = true; break; default: - print("mousePressEvent()...... " + overlayNames[result.overlayID]); + if (wantDebug) { + print("mousePressEvent()...... " + overlayNames[result.overlayID]); + } mode = "UNKNOWN"; break; } diff --git a/examples/shaders/rainyDayNightSkybox.fs b/examples/shaders/rainyDayNightSkybox.fs new file mode 100644 index 0000000000..265cda3e25 --- /dev/null +++ b/examples/shaders/rainyDayNightSkybox.fs @@ -0,0 +1,125 @@ +// Created by inigo quilez - iq/2013 +// Turbulence and Day/Night cycle added by Michael Olson - OMGparticles/2015 +// rain effect adapted from Rainy London by David Hoskins. - https://www.shadertoy.com/view/XdSGDc +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +#line 6 +const float PI = 3.14159; +uniform float rotationSpeed = 0.005; +uniform float gridLevel = 0.0; +uniform float constellationLevel = 0.0; +uniform float constellationBoundaryLevel = 0.0; + +// Axial tilt +const float axialTilt = 0.408407; +const vec2 atsc = vec2(sin(axialTilt), cos(axialTilt)); +const mat3 axialTiltMatrix = mat3( + vec3(atsc.y, -atsc.x, 0.0), + vec3(atsc.x, atsc.y, 0.0), + vec3(0.0, 0.0, 1.0)); + +vec2 directionToSpherical(in vec3 d) { + return vec2(atan(d.x, -d.z), asin(d.y)) * -1.0f; +} + +vec2 directionToUv(in vec3 d) { + vec2 uv = directionToSpherical(d); + uv /= PI; + uv.x /= 2.0; + uv += 0.5; + return uv; +} + +vec3 stars3(in vec3 d) { + //return rd; + vec3 dir = d * axialTiltMatrix; + + float theta = rotationSpeed * iGlobalTime * 4.0; + vec2 sc = vec2(sin(theta), cos(theta)); + dir = dir * mat3( vec3(sc.y, 0.0, sc.x), + vec3(0.0, 1.0, 0.0), + vec3(-sc.x, 0.0, sc.y)); + + vec2 uv = directionToUv(dir); + vec3 starColor = texture2D( iChannel0, uv).xyz; + starColor = pow(starColor, vec3(0.75)); + + if (gridLevel > 0.0) { + starColor += texture2D( iChannel1, uv).xyz * gridLevel; + } + return starColor; +} + +//const vec3 vNightColor = vec3(.15, 0.3, 0.6); +uniform vec3 uDayColor = vec3(0.9,0.7,0.7); +const vec3 vNightColor = vec3(1.0, 1.0, 1.0); +const vec3 vHorizonColor = vec3(0.6, 0.3, 0.4); +const vec3 vSunColor = vec3(1.0,0.8,0.6); +const vec3 vSunRimColor = vec3(1.0,0.66,0.33); + +vec4 render( in vec3 ro, in vec3 rd ) +{ + float fSunSpeed = (rotationSpeed * 10.0 * iGlobalTime) + PI / 2.0 * 3.0; + vec3 sundir = normalize( vec3(cos(fSunSpeed),sin(fSunSpeed),0.0) ); + sundir = sundir * axialTiltMatrix; + float sun = clamp( dot(sundir,rd), 0.0, 1.0 ); + + float fSunHeight = sundir.y; + + // below this height will be full night color + float fNightHeight = -0.8; + // above this height will be full day color + float fDayHeight = 0.3; + + float fHorizonLength = fDayHeight - fNightHeight; + float fInverseHL = 1.0 / fHorizonLength; + float fHalfHorizonLength = fHorizonLength / 2.0; + float fInverseHHL = 1.0 / fHalfHorizonLength; + float fMidPoint = fNightHeight + fHalfHorizonLength; + + float fNightContrib = clamp((fSunHeight - fMidPoint) * (-fInverseHHL), 0.0, 1.0); + float fHorizonContrib = -clamp(abs((fSunHeight - fMidPoint) * (-fInverseHHL)), 0.0, 1.0) + 1.0; + float fDayContrib = clamp((fSunHeight - fMidPoint) * ( fInverseHHL), 0.0, 1.0); + + // sky color + vec3 vSkyColor = vec3(0.0); + vSkyColor += mix(vec3(0.0), vNightColor, fNightContrib); // Night + vSkyColor += mix(vec3(0.0), vHorizonColor, fHorizonContrib); // Horizon + vSkyColor += mix(vec3(0.0), uDayColor, fDayContrib); // Day + vec3 col = vSkyColor; + + // atmosphere brighter near horizon + col -= clamp(rd.y, 0.0, 0.5); + + // draw sun + col += 0.4 * vSunRimColor * pow( sun, 4.0 ); + col += 1.0 * vSunColor * pow( sun, 2000.0 ); + + // stars + float fStarContrib = clamp((fSunHeight - fDayHeight) * (-fInverseHL), 0.0, 1.0); + if (fStarContrib > 0.0) { + vec3 vStarDir = rd; + col = mix(col, stars3(vStarDir), fStarContrib); + } + + // Ten layers of rain sheets... + float rainBrightness = 0.15; + vec2 q = vec2(atan(rd.x, -rd.z), asin(rd.y)); + float dis = 1; + int sheets = 12; + for (int i = 0; i < sheets; i++) { + float f = pow(dis, .45) + 0.25; + vec2 st = f * (q * vec2(2.0, .05) + vec2(-iGlobalTime*.01 + q.y * .05, iGlobalTime * 0.12)); + f = texture2D(iChannel2, st * .5, -59.0).x; + f += texture2D(iChannel2, st*.284, -99.0).y; + f = clamp(pow(abs(f)*.5, 29.0) * 140.0, 0.00, q.y*.4+.05); + vec3 bri = vec3(rainBrightness); + col += bri*f; + dis += 3.5; + } + + return vec4(clamp(col, 0.0, 1.0), 1.0); +} + +vec3 getSkyboxColor() { + return render( vec3(0.0), normalize(_normal) ).rgb; +} diff --git a/examples/tests/avatarAttachmentTest.js b/examples/tests/avatarAttachmentTest.js index 23b94ba8d5..f287013979 100644 --- a/examples/tests/avatarAttachmentTest.js +++ b/examples/tests/avatarAttachmentTest.js @@ -17,14 +17,14 @@ function ToggleButtonBuddy(x, y, width, height, urls) { this.up = Overlays.addOverlay("image", { x: x, y: y, width: width, height: height, - subImage: { x: 0, y: height, width: width, height: height}, + subImage: { x: 0, y: 0, width: width, height: height}, imageURL: urls.up, visible: true, alpha: 1.0 }); this.down = Overlays.addOverlay("image", { x: x, y: y, width: width, height: height, - subImage: { x: 0, y: height, width: width, height: height}, + subImage: { x: 0, y: 0, width: width, height: height}, imageURL: urls.down, visible: false, alpha: 1.0 @@ -120,7 +120,6 @@ coatButton.addToggleHandler(function (isDown) { } }); - function wearAttachment(attachment) { MyAvatar.attach(attachment.modelURL, attachment.jointName, @@ -144,5 +143,5 @@ function removeAttachment(attachment) { Script.scriptEnding.connect(function() { hatButton.destroy(); - coatbutton.destroy(); + coatButton.destroy(); }); diff --git a/examples/tests/particleOrientationTest.js b/examples/tests/particleOrientationTest.js index 7fe670f4b7..97915c79e3 100644 --- a/examples/tests/particleOrientationTest.js +++ b/examples/tests/particleOrientationTest.js @@ -46,7 +46,7 @@ function emitter(jointName) { x:0, y: 0, z: 0, - w: Math.PI + w: 1 }, emitRadiusStart: 0, polarStart: 0, @@ -84,7 +84,7 @@ function emitter(jointName) { alpha: 1, alphaSpread: 0, alphaStart: 1, - alphaFinish: 1 + alphaFinish: 1 }); return newEmitter; } diff --git a/examples/toybox/flappyAvatars/flappyAvatars.js b/examples/toybox/flappyAvatars/flappyAvatars.js new file mode 100644 index 0000000000..06a0a8e751 --- /dev/null +++ b/examples/toybox/flappyAvatars/flappyAvatars.js @@ -0,0 +1,689 @@ +// +// flappyAvatars.js +// examples/toybox +// +// Created by Clement 3/2/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 +// + +(function() { + // Constants + var TRIGGER_CONTROLS = [ + Controller.Standard.LT, + Controller.Standard.RT, + ]; + + var G = 4.0; + + Number.prototype.clamp = function(min, max) { + return Math.min(Math.max(this, min), max); + }; + + var entityManager = new EntityManager(); + + // Class definitions + function Avatar(DEFAULT_X, DEFAULT_Y, rotation, to3DPosition) { + var DIMENSION = 0.15; + var JUMP_VELOCITY = 1.0; + var xPosition = DEFAULT_X; + var color = { red: 0, green: 0, blue: 255 }; + + var dimensionsSet = false; + var dimensions = { x: DIMENSION, y: DIMENSION, z: DIMENSION }; + var yPosition = DEFAULT_Y; + var yVelocity = 0.0; + var yAcceleration = -G; + + var airSwipeSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/Air Swipe 05.wav"); + var injector = null; + + this.position = function() { + return { x: xPosition, y: yPosition }; + } + this.dimensions = function() { + return dimensions; + } + + var id = entityManager.add({ + type: "Model", + modelURL: MyAvatar.skeletonModelURL, + animation: { + url: "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/fly.fbx", + running: true, + fps: 30, + firstFrame: 1.0, + lastFrame: 80.0, + currentFrame: 1.0, + loop: true, + hold: false + }, + position: to3DPosition(this.position()), + rotation: rotation, + dimensions: dimensions + }); + + this.changeModel = function(modelURL) { + dimensionsSet = false; + dimensions = { x: 0.10, y: 0.10, z: 0.01 }; + + Entities.editEntity(id, { + modelURL: modelURL, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, -90, 0)), + dimensions: dimensions, + animation: {running: false} + }); + + airSwipeSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/8bit Jump 03.wav"); + injector = null; + } + + this.jump = function() { + yVelocity = JUMP_VELOCITY; + + if (airSwipeSound.downloaded && !injector) { + injector = Audio.playSound(airSwipeSound, { position: to3DPosition(this.position()), volume: 0.05 }); + } else if (injector) { + injector.restart(); + } + } + this.update = function(deltaTime) { + if (!dimensionsSet) { + var properties = Entities.getEntityProperties(id, ["naturalDimensions"]); + var naturalDimensions = properties.naturalDimensions; + if (naturalDimensions.x != 1 || naturalDimensions.y != 1 || naturalDimensions.z != 1) { + var max = Math.max(naturalDimensions.x, Math.max(naturalDimensions.y, naturalDimensions.z)); + dimensions.x = naturalDimensions.x / max * dimensions.x; + dimensions.y = naturalDimensions.y / max * dimensions.y; + dimensions.z = naturalDimensions.z / max * dimensions.z; + dimensionsSet = true; + + Entities.editEntity(id, { + dimensions: dimensions + }); + } + } else { + dimensions = Entities.getEntityProperties(id, ["dimensions"]).dimensions; + } + + yPosition += deltaTime * (yVelocity + deltaTime * yAcceleration / 2.0); + yVelocity += deltaTime * yAcceleration; + } + this.draw = function() { + Entities.editEntity(id, { + position: to3DPosition(this.position()) + }); + } + this.reset = function() { + yPosition = DEFAULT_Y; + yVelocity = 0.0; + } + } + + function Coin(xPosition, yPosition, to3DPosition) { + var velocity = 0.4; + var dimensions = { x: 0.0625, y: 0.0625, z: 0.0088 }; + + this.position = function() { + return { x: xPosition, y: yPosition }; + } + + var id = entityManager.add({ + type: "Model", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/coin.fbx", + angularVelocity: { x: 0, y: 20, z: 0 }, + position: to3DPosition(this.position()), + dimensions:dimensions + }); + + this.update = function(deltaTime) { + xPosition -= deltaTime * velocity; + } + this.isColliding = function(avatar) { + var deltaX = Math.abs(this.position().x - avatar.position().x); + var deltaY = Math.abs(this.position().Y - avatar.position().Y); + if (deltaX < (avatar.dimensions().x + dimensions.x) / 2.0 && + deltaX < (avatar.dimensions().y + dimensions.y) / 2.0) { + return true; + } + + return false; + } + this.draw = function() { + Entities.editEntity(id, { position: to3DPosition({ x: xPosition, y: yPosition }) }); + } + this.clear = function() { + entityManager.remove(id); + } + } + + function Pipe(xPosition, yPosition, height, gap, to3DPosition) { + var velocity = 0.4; + var width = 0.1; + + this.position = function() { + return xPosition; + } + + var upHeight = yPosition - (height + gap); + var upYPosition = height + gap + upHeight / 2.0; + + var idUp = entityManager.add({ + type: "Model", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/greenPipe.fbx", + rotation: Quat.fromPitchYawRollDegrees(180, 0, 0), + position: to3DPosition({ x: xPosition, y: upYPosition }), + dimensions: { x: width, y: upHeight, z: width } + }); + var idDown = entityManager.add({ + type: "Model", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/greenPipe.fbx", + position: to3DPosition({ x: xPosition, y: height / 2.0 }), + dimensions: { x: width, y: height, z: width } + }); + + this.update = function(deltaTime) { + xPosition -= deltaTime * velocity; + } + this.isColliding = function(avatar) { + var deltaX = Math.abs(this.position() - avatar.position().x); + if (deltaX < (avatar.dimensions().z + width) / 2.0) { + var factor = 0.8; + var upDistance = (yPosition - upHeight) - (avatar.position().y + avatar.dimensions().y * factor); + var downDistance = (avatar.position().y - avatar.dimensions().y * factor) - height; + if (upDistance <= 0 || downDistance <= 0) { + return true; + } + } + + return false; + } + this.draw = function() { + Entities.editEntity(idUp, { position: to3DPosition({ x: xPosition, y: upYPosition }) }); + Entities.editEntity(idDown, { position: to3DPosition({ x: xPosition, y: height / 2.0 }) }); + } + this.clear = function() { + entityManager.remove(idUp); + entityManager.remove(idDown); + } + } + + function Pipes(newPipesPosition, newPipesHeight, to3DPosition, moveScore) { + var lastPipe = 0; + var pipesInterval = 2.0; + + var pipes = new Array(); + var coins = new Array(); + + var coinsSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/Coin.wav"); + var injector = null; + + this.update = function(deltaTime, gameTime, startedPlaying) { + // Move pipes forward + pipes.forEach(function(element) { + element.update(deltaTime); + }); + // Move coins forward + coins.forEach(function(element) { + element.update(deltaTime); + }); + // Delete pipes over the end + var count = 0; + while(count < pipes.length && pipes[count].position() <= 0.0) { + pipes[count].clear(); + count++; + } + if (count > 0) { + pipes = pipes.splice(count); + } + // Delete coins over the end + count = 0; + while(count < coins.length && coins[count].position() <= 0.0) { + coins[count].clear(); + count++; + } + if (count > 0) { + coins = coins.splice(count); + } + // Make new pipes and coins + if (startedPlaying && gameTime - lastPipe > pipesInterval) { + var min = 0.4; + var max = 0.7; + var height = Math.random() * (max - min) + min; + pipes.push(new Pipe(newPipesPosition, newPipesHeight, height, 0.5, to3DPosition)); + coins.push(new Coin(newPipesPosition, height + 0.5 / 2.0, to3DPosition)); + lastPipe = gameTime; + } + } + this.isColliding = function(avatar) { + // Check coin collection + var collected = -1; + coins.forEach(function(element, index) { + if (element.isColliding(avatar)) { + element.clear(); + collected = index; + moveScore(1); + + if (coinsSound.downloaded && !injector) { + injector = Audio.playSound(coinsSound, { position: to3DPosition({ x: newPipesPosition, y: newPipesHeight }), volume: 0.1 }); + } else if (injector) { + injector.restart(); + } + } + }); + if (collected > -1) { + coins.splice(collected, 1); + } + + + // Check collisions + var isColliding = false; + + pipes.forEach(function(element) { + isColliding |= element.isColliding(avatar); + }); + + return isColliding; + } + this.draw = function() { + // Drawing pipes + pipes.forEach(function(element) { + element.draw(); + }); + // Drawing coins + coins.forEach(function(element) { + element.draw(); + }); + } + this.clear = function() { + // Clearing pipes + pipes.forEach(function(element) { + element.clear(); + }); + pipes = new Array(); + + // Clearing coins + coins.forEach(function(element) { + element.clear(); + }); + coins = new Array(); + } + } + + + function Score(space, bestScore) { + var score = 0; + var highScore = bestScore; + + var topOffset = Vec3.multiplyQbyV(space.orientation, { x: -0.1, y: 0.2, z: -0.2 }); + var topLeft = Vec3.sum(space.position, topOffset); + var bottomOffset = Vec3.multiplyQbyV(space.orientation, { x: -0.1, y: 0.0, z: -0.2 }); + var bottomLeft = Vec3.sum(space.position, bottomOffset); + + var numberDimensions = { x: 0.0660, y: 0.1050, z: 0.0048 }; + + function numberUrl(number) { + return "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/" + number + ".fbx" + } + function digitPosition(digit) { + return Vec3.multiplyQbyV(space.orientation, { x: 0.3778 + digit * (numberDimensions.x + 0.01), y: 0.0, z: 0.0 }); + } + this.score = function() { + return score; + } + this.highScore = function() { + return highScore; + } + + var numDigits = 3; + + var bestId = entityManager.add({ + type: "Model", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/best.fbx", + position: topLeft, + rotation: Quat.multiply(space.orientation, Quat.fromPitchYawRollDegrees(90, 0, 0)), + dimensions: { x: 0.2781, y: 0.0063, z: 0.1037 } + }); + var bestDigitsId = [] + for (var i = 0; i < numDigits; i++) { + bestDigitsId[i] = entityManager.add({ + type: "Model", + modelURL: numberUrl(0), + position: Vec3.sum(topLeft, digitPosition(i)), + rotation: space.orientation, + dimensions: numberDimensions + }); + } + + var scoreId = entityManager.add({ + type: "Model", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/score.fbx", + position: bottomLeft, + rotation: Quat.multiply(space.orientation, Quat.fromPitchYawRollDegrees(90, 0, 0)), + dimensions: { x: 0.3678, y: 0.0063, z: 0.1037 } + }); + var scoreDigitsId = [] + for (var i = 0; i < numDigits; i++) { + scoreDigitsId[i] = entityManager.add({ + type: "Model", + modelURL: numberUrl(0), + position: Vec3.sum(bottomLeft, digitPosition(i)), + rotation: space.orientation, + dimensions: numberDimensions + }); + } + + this.moveScore = function(delta) { + score += delta; + if (score > highScore) { + highScore = score; + } + } + this.resetScore = function() { + score = 0; + } + + this.draw = function() { + for (var i = 0; i < numDigits; i++) { + Entities.editEntity(bestDigitsId[i], { modelURL: numberUrl(Math.floor((highScore / Math.pow(10, numDigits- i - 1)) % 10)) }); + } + + for (var i = 0; i < numDigits; i++) { + Entities.editEntity(scoreDigitsId[i], { modelURL: numberUrl(Math.floor(score / Math.pow(10, numDigits - i - 1)) % 10) }); + } + } + } + + function Game(bestScore) { + // public methods + this.start = function() { + if (!isRunning) { + isRunning = true; + setup(); + } + } + + this.stop = function() { + if (isRunning) { + cleanup(); + isRunning = false; + } + } + + // Game loop setup + var timestamp = 0; + this.idle = function(triggerValue) { + var now = Date.now(); + var deltaTime = (now - timestamp) / 1000.0; + if (timestamp === 0) { + deltaTime = 0; + } + gameTime += deltaTime; + + inputs(triggerValue); + update(deltaTime); + draw(); + timestamp = now; + } + + // Constants + var spaceDimensions = { x: 2.0, y: 1.5, z: 0.01 }; + var spaceDistance = 1.5; + var spaceYOffset = 0.6; + + // Private game state + var that = this; + var isRunning = false; + var startedPlaying = false; + + var coolDown = 1.5; + var lastLost = -coolDown; + + var gameTime = 0; + + var isJumping = false; + var lastJumpValue = 0.0; + var lastTriggerValue = 0.0; + var TRIGGER_THRESHOLD = 0.9; + + var space = null; + var avatar = null; + var pipes = null; + var score = null; + + var gameOverSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/Game Over.wav"); + var injector = null; + + var directions = ["UP", "DOWN", "LEFT", "RIGHT"]; + var sequence = [directions[0], directions[0], directions[1], directions[1], directions[2], directions[3], directions[2], directions[3], "b", "a"]; + var current = 0; + function keyPress(event) { + if (event.text === sequence[current]) { + ++current; + } else { + current = 0; + } + if (current === sequence.length) { + avatar.changeModel("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/mario.fbx"); + current = 0; + } + } + + var isBoardReset = true; + + function moveScore(delta) { + score.moveScore(delta); + } + + this.score = function() { + return score.score(); + } + this.highScore = function() { + return score.highScore(); + } + + function setup() { + space = { + position: getSpacePosition(), + orientation: getSpaceOrientation(), + dimensions: getSpaceDimensions() + } + + var rotation = Quat.multiply(space.orientation, Quat.fromPitchYawRollDegrees(0, 90, 0)); + avatar = new Avatar(space.dimensions.x / 2.0, space.dimensions.y / 2.0, rotation, to3DPosition); + pipes = new Pipes(space.dimensions.x, space.dimensions.y, to3DPosition, moveScore); + score = new Score(space, bestScore); + + Controller.keyPressEvent.connect(keyPress); + } + function inputs(triggerValue) { + if (!startedPlaying && !isBoardReset && (gameTime - lastLost) > coolDown) { + score.resetScore(); + avatar.reset(); + pipes.clear(); + + isBoardReset = true; + } + + if (triggerValue > TRIGGER_THRESHOLD && + lastTriggerValue < TRIGGER_THRESHOLD && + (gameTime - lastLost) > coolDown) { + isJumping = true; + startedPlaying = true; + } + lastTriggerValue = triggerValue; + } + function update(deltaTime) { + // Keep entities alive + entityManager.update(deltaTime); + + if (!startedPlaying && (gameTime - lastLost) < coolDown && !isBoardReset) { + return; + } + + // Update Avatar + if (!startedPlaying && avatar.position().y < spaceDimensions.y / 2.0) { + isJumping = true; + } + // Apply jumps + if (isJumping) { + avatar.jump(); + isJumping = false; + } + avatar.update(deltaTime); + + pipes.update(deltaTime, gameTime, startedPlaying); + + // Check lost + var hasLost = avatar.position().y < 0.0 || + avatar.position().y > space.dimensions.y || + pipes.isColliding(avatar); + + + // Cleanup + if (hasLost) { + if (gameOverSound.downloaded && !injector) { + injector = Audio.playSound(gameOverSound, { position: space.position, volume: 0.4 }); + } else if (injector) { + injector.restart(); + } + + isBoardReset = false; + startedPlaying = false; + lastLost = gameTime; + } + } + function draw() { + avatar.draw(); + pipes.draw(); + score.draw(); + } + function cleanup() { + entityManager.removeAll(); + + Controller.keyPressEvent.disconnect(keyPress); + } + + // Private methods + function getSpacePosition() { + var forward = Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.FRONT); + var spacePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(spaceDistance, forward)); + return Vec3.sum(spacePosition, Vec3.multiply(spaceYOffset, Vec3.UP)); + } + function getSpaceOrientation() { + return MyAvatar.orientation; + } + function getSpaceDimensions() { + return spaceDimensions; + } + + function to3DPosition(position) { + var position2D = { + x: position.x - space.dimensions.x / 2.0, + y: position.y - space.dimensions.y / 2.0, + z: 0.0 + } + return Vec3.sum(space.position, Vec3.multiplyQbyV(space.orientation, position2D)); + } + } + + function EntityManager() { + var OBJECTS_LIFETIME = 1; + var entities = new Array(); + var lifetime = OBJECTS_LIFETIME; + + this.setLifetime = function(newLifetime) { + lifetime = newLifetime; + this.update(); + } + this.add = function(properties) { + // Add to scene + properties.lifetime = lifetime; + var entityID = Entities.addEntity(properties); + // Add to array + entities.push({ id: entityID, properties: properties }); + + return entityID; + } + this.update = function(deltaTime) { + entities.forEach(function(element) { + // Get entity's age + var properties = Entities.getEntityProperties(element.id, ["age"]); + // Update entity's lifetime + Entities.editEntity(element.id, { lifetime: properties.age + lifetime }); + }); + } + this.remove = function(entityID) { + // Remove from scene + Entities.deleteEntity(entityID); + + // Remove from array + entities = entities.filter(function(element) { + return element.id !== entityID; + }); + } + this.removeAll = function() { + // Remove all from scene + entities.forEach(function(element) { + Entities.deleteEntity(element.id); + }); + // Remove all from array + entities = new Array(); + } + } + + PartableGame = function() { + this.entityID = null; + this.equipped = false; + this.triggerValue = 0.0; + this.hand = 0; + this.game = null; + }; + + PartableGame.prototype = { + preload: function(entityID) { + this.entityID = entityID; + }, + unload: function() { + }, + startEquip: function(id, params) { + this.equipped = true; + this.hand = params[0] == "left" ? 0 : 1; + + + var bestScore = 0; + var properties = Entities.getEntityProperties(this.entityID, ["userData"]); + var userData = JSON.parse(properties.userData); + if (userData.highScore) { + bestScore = userData.highScore; + } + + this.game = new Game(bestScore); + this.game.start(); + }, + releaseEquip: function(id, params) { + this.equipped = false; + + var properties = Entities.getEntityProperties(this.entityID, ["userData"]); + var userData = JSON.parse(properties.userData); + userData.highScore = this.game.highScore(); + properties.userData = JSON.stringify(userData); + Entities.editEntity(this.entityID, properties); + + this.game.stop(); + delete this.game; + }, + continueEquip: function(id, params) { + if (!this.equipped) { + return; + } + this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.hand]); + this.game.idle(this.triggerValue); + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new PartableGame(); +}); diff --git a/examples/toybox/flappyAvatars/flappyAvatars.json b/examples/toybox/flappyAvatars/flappyAvatars.json new file mode 100644 index 0000000000..ed6dae8851 --- /dev/null +++ b/examples/toybox/flappyAvatars/flappyAvatars.json @@ -0,0 +1,34 @@ +{ + "Entities": [ + { + "collisionsWillMove": 1, + "created": "2016-03-03T19:00:10Z", + "dimensions": { + "x": 0.11497055739164352, + "y": 0.11497056484222412, + "z": 0.11497056484222412 + }, + "dynamic": 1, + "id": "{ee5b25e6-aca2-4dc7-9462-51537d89c126}", + "modelURL": "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/cube.fbx", + "queryAACube": { + "scale": 0.5974045991897583, + "x": -5.1575918197631836, + "y": 23.078603744506836, + "z": 16.521066665649414 + }, + "rotation": { + "w": 0.92288088798522949, + "x": -0.10148775577545166, + "y": -0.13279926776885986, + "z": 0.34688329696655273 + }, + "script": "https://raw.githubusercontent.com/Atlante45/hifi/feat/hackaton/examples/toybox/flappyAvatars/flappyAvatars.js", + "scriptTimestamp": 1457031937425, + "shapeType": "box", + "type": "Model", + "userData": "{\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.07079616189002991,\"y\":0.20177987217903137,\"z\":0.06374628841876984},{\"x\":-0.5863648653030396,\"y\":-0.46007341146469116,\"z\":0.46949487924575806,\"w\":-0.4733745753765106}],\"LeftHand\":[{\"x\":-0.018704339861869812,\"y\":0.20499876141548157,\"z\":0.08445858210325241},{\"x\":0.2061777561903,\"y\":-0.6629757881164551,\"z\":0.5865269303321838,\"w\":0.41706138849258423}]}},\"grabbableKey\":{\"invertSolidWhileHeld\":true},\"resetMe\":{\"resetMe\":true},\"highScore\":0}" + } + ], + "Version": 57 +} diff --git a/examples/toybox/flashlight/flashlight.js b/examples/toybox/flashlight/flashlight.js index 19cbc422f4..02a2e07985 100644 --- a/examples/toybox/flashlight/flashlight.js +++ b/examples/toybox/flashlight/flashlight.js @@ -104,8 +104,9 @@ green: 255, blue: 255 }, - intensity: 2, - exponent: 0.3, + intensity: 1, + falloffRadius:0.9, + exponent: 0.5, cutoff: 20, lifetime: LIFETIME, position: lightTransform.p, @@ -128,6 +129,8 @@ blue: 255 }, exponent: 0, + intensity:1.0, + falloffRadius:0.3, lifetime: LIFETIME, cutoff: 90, // in degrees position: glowLightTransform.p, diff --git a/examples/toybox/musicPlayer/createNewMusicPlayerOnClick.js b/examples/toybox/musicPlayer/createNewMusicPlayerOnClick.js new file mode 100644 index 0000000000..68da1d2628 --- /dev/null +++ b/examples/toybox/musicPlayer/createNewMusicPlayerOnClick.js @@ -0,0 +1,100 @@ +// +// createNewMusicPlayerOnClick.js +// +// Created by Brad Hefta-Gaub on 3/3/16. +// Copyright 2016 High Fidelity, Inc. +// +// Entity Script that you can attach to any entity to have it spawn new "music players" +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +(function(){ + + var musicPlayerScript = Script.resolvePath("./musicPlayer.js"); + var imageShader = Script.resolvePath("./imageShader.fs"); + var defaultImage = Script.resolvePath("./defaultImage.jpg"); + + function getPositionToCreateEntity() { + var distance = 0.5; + var direction = Quat.getFront(Camera.orientation); + var offset = Vec3.multiply(distance, direction); + var placementPosition = Vec3.sum(Camera.position, offset); + + var cameraPosition = Camera.position; + + var HALF_TREE_SCALE = 16384; + + var cameraOutOfBounds = Math.abs(cameraPosition.x) > HALF_TREE_SCALE || Math.abs(cameraPosition.y) > HALF_TREE_SCALE || Math.abs(cameraPosition.z) > HALF_TREE_SCALE; + var placementOutOfBounds = Math.abs(placementPosition.x) > HALF_TREE_SCALE || Math.abs(placementPosition.y) > HALF_TREE_SCALE || Math.abs(placementPosition.z) > HALF_TREE_SCALE; + + if (cameraOutOfBounds && placementOutOfBounds) { + return null; + } + + placementPosition.x = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.x)); + placementPosition.y = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.y)); + placementPosition.z = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.z)); + + return placementPosition; + } + + function createNewIPOD() { + var iPodPosition = { x: 0, y: .5, z: 0}; + var iPodDimensions = { x: 0.15, y: 0.3, z: 0.03 }; + var overlayDimensions = { x: 0.13, y: 0.13, z: 0.001 }; + var boxOverlayDimensions = { x: 0.13, y: 0.13, z: 0.0001 }; + + var iPodID = Entities.addEntity({ + type: "Box", + name: "music player", + position: iPodPosition, + dimensions: iPodDimensions, + color: { red: 150, green: 150, blue: 150}, + script: musicPlayerScript, + dynamic: true + }); + print(iPodID); + + var textID = Entities.addEntity({ + type: "Text", + name: "now playing", + position: { x: 0, y: (0.5+0.07), z: 0.0222}, + dimensions: overlayDimensions, + color: { red: 150, green: 150, blue: 150}, + parentID: iPodID, + lineHeight: 0.01, + text: "Pause" + }); + + + var newAlbumArt = JSON.stringify( + { + "ProceduralEntity": { + "version":2, + "shaderUrl":imageShader, + "uniforms":{"iSpeed":0,"iShell":1}, + "channels":[defaultImage] + } + }); + + + var albumArtID = Entities.addEntity({ + type: "Box", + name: "album art", + position: { x: 0, y: (0.5-0.07), z: 0.01506}, + dimensions: boxOverlayDimensions, + color: { red: 255, green: 255, blue: 255}, + userData: newAlbumArt, + parentID: iPodID + }); + Entities.editEntity(iPodID, { position: getPositionToCreateEntity() }); + } + + + this.clickDownOnEntity = function(myID, mouseEvent) { + createNewIPOD(); + }; +}) + + diff --git a/examples/toybox/musicPlayer/defaultImage.jpg b/examples/toybox/musicPlayer/defaultImage.jpg new file mode 100644 index 0000000000..54de95bf42 Binary files /dev/null and b/examples/toybox/musicPlayer/defaultImage.jpg differ diff --git a/examples/toybox/musicPlayer/imageShader.fs b/examples/toybox/musicPlayer/imageShader.fs new file mode 100644 index 0000000000..7c1aba248d --- /dev/null +++ b/examples/toybox/musicPlayer/imageShader.fs @@ -0,0 +1,44 @@ +float aspect(vec2 v) { + return v.x / v.y; +} + +vec3 indexedTexture() { + vec2 uv = _position.xy; + uv += 0.5; + uv.y = 1.0 - uv.y; + + float targetAspect = iWorldScale.x / iWorldScale.y; + float sourceAspect = aspect(iChannelResolution[0].xy); + float aspectCorrection = sourceAspect / targetAspect; + if (aspectCorrection > 1.0) { + float offset = aspectCorrection - 1.0; + float halfOffset = offset / 2.0; + uv.y -= halfOffset; + uv.y *= aspectCorrection; + } else { + float offset = 1.0 - aspectCorrection; + float halfOffset = offset / 2.0; + uv.x -= halfOffset; + uv.x /= aspectCorrection; + } + + if (any(lessThan(uv, vec2(0.0)))) { + return vec3(0.0); + } + + if (any(greaterThan(uv, vec2(1.0)))) { + return vec3(0.0); + } + + vec4 color = texture(iChannel0, uv); + return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x)); +} + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + if (_position.z > -0.49) { + discard; + } + + specular = indexedTexture(); + return 1.0; +} \ No newline at end of file diff --git a/examples/toybox/musicPlayer/musicPlayer.js b/examples/toybox/musicPlayer/musicPlayer.js new file mode 100644 index 0000000000..4a7655efcc --- /dev/null +++ b/examples/toybox/musicPlayer/musicPlayer.js @@ -0,0 +1,330 @@ +// +// musicPlayer.js +// +// Created by Brad Hefta-Gaub on 3/3/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 + +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ + +(function() { + + var imageShader = Script.resolvePath("./imageShader.fs"); + var defaultImage = Script.resolvePath("./defaultImage.jpg"); + + var MAPPING_NAME = "com.highfidelity.musicPlayerEntity"; + + var PLAYLIST_URL = "https://spreadsheets.google.com/feeds/cells/1x-ceGPGHldkHadARABFWfujLPTOWzXJPhrf2bTwg2cQ/od6/public/basic?alt=json"; + var SONG_VOLUME = 0.1; + var HEADPHONES_ATTACHMENT = { + modelURL: "https://s3.amazonaws.com/hifi-public/brad/musicplayer/headphones2-v2.fbx", + jointName: "Head", + translation: {"x": 0, "y": 0.19, "z": 0.06}, + rotation: {"x":0,"y":0.7071067690849304,"z":0.7071067690849304,"w":0}, + scale: 0.435, + isSoft: false + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Various helper functions... + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Helper function for returning either a value, or the default value if the value is undefined. This is + // is handing in parsing JSON where you don't know if the values have been set or not. + function valueOrDefault(value, defaultValue) { + if (value !== undefined) { + return value; + } + return defaultValue; + } + + // return a random float between high and low + function randFloat(low, high) { + return low + Math.random() * (high - low); + } + + // wears an attachment on MyAvatar + function wearAttachment(attachment) { + MyAvatar.attach(attachment.modelURL, + attachment.jointName, + attachment.translation, + attachment.rotation, + attachment.scale, + attachment.isSoft); + } + + // un-wears an attachment from MyAvatar + function removeAttachment(attachment) { + var attachments = MyAvatar.attachmentData; + var i, l = attachments.length; + for (i = 0; i < l; i++) { + if (attachments[i].modelURL === attachment.modelURL) { + attachments.splice(i, 1); + MyAvatar.attachmentData = attachments; + break; + } + } + } + + var _this; + MusicPlayer = function() { + _this = this; + this.equipped = false; + }; + + MusicPlayer.prototype = { + preload: function(entityID) { + print("preload"); + //print("rotation:" + JSON.stringify(Quat.fromPitchYawRollDegrees(-90,180,0))); + this.entityID = entityID; + + // Get the entities userData property, to see if someone has overridden any of our default settings + var userDataText = Entities.getEntityProperties(entityID, ["userData"]).userData; + //print(userDataText); + var userData = {}; + if (userDataText !== "") { + //print("calling JSON.parse"); + userData = JSON.parse(userDataText); + //print("userData:" + JSON.stringify(userData)); + } + var musicPlayerUserData = valueOrDefault(userData.musicPlayer, {}); + this.headphonesAttachment = valueOrDefault(musicPlayerUserData.headphonesAttachment, HEADPHONES_ATTACHMENT); + + this.track = 0; // start at the first track + this.playlistURL = valueOrDefault(musicPlayerUserData.playlistURL, PLAYLIST_URL); + this.songVolume = valueOrDefault(musicPlayerUserData.songVolume, SONG_VOLUME); + this.songPlaying = false; + + this.loadPlayList(); + + // Find my screen and any controlls + var children = Entities.getChildrenIDsOfJoint(entityID, 65535); + for (var child in children) { + var childID = children[child]; + var childProperties = Entities.getEntityProperties(childID,["type", "name"]); + if (childProperties.type == "Text" && childProperties.name == "now playing") { + this.nowPlayingID = childID; + } + if (childProperties.type == "Box" && childProperties.name == "album art") { + this.albumArt = childID; + } + } + }, + + unload: function() { + print("unload"); + if (_this.songInjector !== undefined) { + _this.songInjector.stop(); + } + }, + + loadPlayList: function() { + print("loadPlayList"); + var req = new XMLHttpRequest(); + req.open("GET", _this.playlistURL, false); + req.send(); + + var entries = JSON.parse(req.responseText).feed.entry; + + for (entry in entries) { + var cellDetails = entries[entry]; + var cellName = cellDetails.title.$t; + var column = Number(cellName.charCodeAt(0)) - Number("A".charCodeAt(0)); + var row = Number(cellName.slice(1)) - 1; + var cellContent = cellDetails.content.$t; + //print(JSON.stringify(cellDetails)); + //print("["+column +"/"+ row +":"+cellContent+"]"); + if (_this.playList === undefined) { + _this.playList = new Array(); + } + if (_this.playList[row] === undefined) { + _this.playList[row] = { }; + } + switch (column) { + case 0: + _this.playList[row].title = cellContent; + break; + case 1: + _this.playList[row].artist = cellContent; + break; + case 2: + _this.playList[row].album = cellContent; + break; + case 3: + _this.playList[row].url = cellContent; + _this.playList[row].sound = SoundCache.getSound(cellContent); + break; + case 4: + _this.playList[row].albumArtURL = cellContent; + break; + } + } + //print(req.responseText); + print(JSON.stringify(_this.playList)); + }, + + startEquip: function(id, params) { + var whichHand = params[0]; // "left" or "right" + print("I am equipped in the " + whichHand + " hand...."); + this.equipped = true; + this.hand = whichHand; + + this.loadPlayList(); // reload the playlist in case... + + this.mapHandButtons(whichHand); + wearAttachment(HEADPHONES_ATTACHMENT); + }, + + continueEquip: function(id, params) { + if (!this.equipped) { + return; + } + }, + releaseEquip: function(id, params) { + print("I am NO LONGER equipped...."); + this.hand = null; + this.equipped = false; + Controller.disableMapping(MAPPING_NAME); + removeAttachment(HEADPHONES_ATTACHMENT); + this.pause(); + }, + + mapHandButtons: function(hand) { + var mapping = Controller.newMapping(MAPPING_NAME); + if (hand === "left") { + mapping.from(Controller.Standard.LS).peek().to(this.playOrPause); + mapping.from(Controller.Standard.LX).peek().to(this.seek); + mapping.from(Controller.Standard.LY).peek().to(this.volume); + } else { + mapping.from(Controller.Standard.RS).peek().to(this.playOrPause); + mapping.from(Controller.Standard.RX).peek().to(this.seek); + mapping.from(Controller.Standard.RY).peek().to(this.volume); + } + Controller.enableMapping(MAPPING_NAME); + }, + + playOrPause: function(value) { + print("[playOrPause: "+value+"]"); + if (value === 1) { + if (!_this.songPlaying) { + _this.play(); + } else { + _this.pause(); + } + } + }, + + play: function() { + print("play current track:" + _this.track); + if (!_this.playList[_this.track].sound.downloaded) { + print("still waiting on track to download...."); + return; // not yet ready + } + + var statusText = "Song:" + _this.playList[_this.track].title + "\n" + + "Artist:" + _this.playList[_this.track].artist + "\n" + + "Album:" + _this.playList[_this.track].album; + + Entities.editEntity(_this.nowPlayingID, { text: statusText }); + + var newAlbumArt = JSON.stringify( + { + "ProceduralEntity": { + "version":2, + "shaderUrl":imageShader, + "uniforms":{"iSpeed":0,"iShell":1}, + "channels":[_this.playList[_this.track].albumArtURL] + } + }); + + Entities.editEntity(_this.albumArt, { userData: newAlbumArt }); + + + _this.songInjector = Audio.playSound(_this.playList[_this.track].sound, { + position: MyAvatar.position, + volume: _this.songVolume, + loop: false, + localOnly: true + }); + _this.songPlaying = true; + }, + + pause: function() { + print("pause"); + Entities.editEntity(_this.nowPlayingID, { text: "Paused" }); + if (_this.songInjector !== undefined) { + _this.songInjector.stop(); + } + var newAlbumArt = JSON.stringify( + { + "ProceduralEntity": { + "version":2, + "shaderUrl":imageShader, + "uniforms":{"iSpeed":0,"iShell":1}, + "channels":[defaultImage] + } + }); + + Entities.editEntity(_this.albumArt, { userData: newAlbumArt }); + + + _this.songPlaying = false; + }, + + seek: function(value) { + print("[seek: " + value + "]"); + if (value > 0.9) { + _this.next(); + } else if (value < -0.9) { + _this.previous(); + } + }, + + volume: function(value) { + print("adjusting volume disabled because of a bug in audio injectors...."); + /* + var scaledValue = value / 20; + _this.songVolume += scaledValue; + print("[volume: " + value + "] new volume:" + _this.songVolume); + if (_this.songInjector !== undefined) { + print("[volume: attempting to set options...."); + var newOptions = { + position: MyAvatar.position, + volume: _this.songVolume, + loop: false, + localOnly: true + }; + + _this.songInjector.options = newOptions; + print("[volume: attempting to set options.... RESULT:" + JSON.stringify(_this.songInjector.options)); + } + */ + }, + + previous: function() { + print("[previous]"); + _this.pause(); + _this.track--; + if (_this.track < 0) { + _this.track = (_this.playList.length - 1); + } + _this.play(); + }, + + next: function() { + print("[next]"); + _this.pause(); + _this.track++; + if (_this.track >= _this.playList.length) { + _this.track = 0; + } + _this.play(); + }, + + }; + + // entity scripts always need to return a newly constructed object of our type + return new MusicPlayer(); +}); diff --git a/examples/toybox/ping_pong_gun/createPingPongGun.js b/examples/toybox/ping_pong_gun/createPingPongGun.js index d9f47eda37..467a05b5b6 100644 --- a/examples/toybox/ping_pong_gun/createPingPongGun.js +++ b/examples/toybox/ping_pong_gun/createPingPongGun.js @@ -13,51 +13,61 @@ Script.include("../../libraries/utils.js"); var scriptURL = Script.resolvePath('pingPongGun.js'); -var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx' -var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj'; +var MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.fbx' +var COLLISION_HULL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.obj'; var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav'; var center = Vec3.sum(Vec3.sum(MyAvatar.position, { - x: 0, - y: 0.5, - z: 0 + x: 0, + y: 0.5, + z: 0 }), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); var pingPongGun = Entities.addEntity({ - type: "Model", - modelURL: MODEL_URL, - shapeType: 'compound', - compoundShapeURL: COLLISION_HULL_URL, - script: scriptURL, - position: center, - dimensions: { - x: 0.08, - y: 0.21, - z: 0.47 + type: "Model", + modelURL: MODEL_URL, + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, + script: scriptURL, + position: center, + dimensions: { + x: 0.125, + y: 0.3875, + z: 0.9931 + }, + dynamic: true, + collisionSoundURL: COLLISION_SOUND_URL, + userData: JSON.stringify({ + grabbableKey: { + invertSolidWhileHeld: true }, - dynamic: true, - collisionSoundURL: COLLISION_SOUND_URL, - userData: JSON.stringify({ - grabbableKey: { - invertSolidWhileHeld: true - }, - wearable:{joints:{RightHand:[{x:0.1177130937576294, - y:0.12922893464565277, - z:0.08307232707738876}, - {x:0.4934672713279724, - y:0.3605862259864807, - z:0.6394805908203125, - w:-0.4664038419723511}], - LeftHand:[{x:0.09151676297187805, - y:0.13639454543590546, - z:0.09354984760284424}, - {x:-0.19628101587295532, - y:0.6418180465698242, - z:0.2830369472503662, - w:0.6851521730422974}]}} - }) + wearable: { + joints: { + RightHand: [{ + x: 0.1177130937576294, + y: 0.12922893464565277, + z: 0.08307232707738876 + }, { + x: 0.4934672713279724, + y: 0.3605862259864807, + z: 0.6394805908203125, + w: -0.4664038419723511 + }], + LeftHand: [{ + x: 0.09151676297187805, + y: 0.13639454543590546, + z: 0.09354984760284424 + }, { + x: -0.19628101587295532, + y: 0.6418180465698242, + z: 0.2830369472503662, + w: 0.6851521730422974 + }] + } + } + }) }); function cleanUp() { - Entities.deleteEntity(pingPongGun); + Entities.deleteEntity(pingPongGun); } Script.scriptEnding.connect(cleanUp); diff --git a/examples/toybox/ping_pong_gun/pingPongGun.js b/examples/toybox/ping_pong_gun/pingPongGun.js index 29eb2e3057..415913ea6d 100644 --- a/examples/toybox/ping_pong_gun/pingPongGun.js +++ b/examples/toybox/ping_pong_gun/pingPongGun.js @@ -22,8 +22,8 @@ //if the trigger value goes below this value, reload the gun. var RELOAD_THRESHOLD = 0.95; - var GUN_TIP_FWD_OFFSET =-0.35; - var GUN_TIP_UP_OFFSET = 0.040; + var GUN_TIP_FWD_OFFSET = -0.35; + var GUN_TIP_UP_OFFSET = 0.12; var GUN_FORCE = 9; var BALL_RESTITUTION = 0.6; var BALL_LINEAR_DAMPING = 0.4; @@ -45,12 +45,12 @@ green: 255, blue: 255 }; - + var TRIGGER_CONTROLS = [ Controller.Standard.LT, Controller.Standard.RT, ]; - + PingPongGun.prototype = { hand: null, @@ -98,8 +98,8 @@ var properties = { // type: 'Model', // modelURL:PING_PONG_BALL_URL, - shapeType:'sphere', - type:'Sphere', + shapeType: 'sphere', + type: 'Sphere', color: BALL_COLOR, dimensions: BALL_DIMENSIONS, damping: BALL_LINEAR_DAMPING, @@ -125,7 +125,7 @@ Audio.playSound(this.SHOOTING_SOUND, audioProperties); }, - + getGunTipPosition: function(properties) { //the tip of the gun is going to be in a different place than the center, so we move in space relative to the model to find that position var frontVector = Quat.getFront(properties.rotation); @@ -148,4 +148,4 @@ // entity scripts always need to return a newly constructed object of our type return new PingPongGun(); -}); +}); \ No newline at end of file diff --git a/examples/tutorials/fireworks/chapter1/fireworksLaunchButtonEntityScript.js b/examples/tutorials/fireworks/chapter1/fireworksLaunchButtonEntityScript.js new file mode 100644 index 0000000000..1232bfc843 --- /dev/null +++ b/examples/tutorials/fireworks/chapter1/fireworksLaunchButtonEntityScript.js @@ -0,0 +1,29 @@ +// +// fireworksLaunchButtonEntityScript.js +// +// Created by Eric Levin on 3/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This is the chapter 1 entity script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial) +// +// Distributed under the Apache License, Version 2.0. + + (function() { + Script.include("../../libraries/utils.js"); + var _this; + Fireworks = function() { + _this = this; + }; + + Fireworks.prototype = { + + preload: function(entityID) { + _this.entityID = entityID; + + } + + }; + + // entity scripts always need to return a newly constructed object of our type + return new Fireworks(); + }); diff --git a/examples/tutorials/fireworks/chapter1/fireworksLaunchButtonSpawner.js b/examples/tutorials/fireworks/chapter1/fireworksLaunchButtonSpawner.js new file mode 100644 index 0000000000..31d5e00e00 --- /dev/null +++ b/examples/tutorials/fireworks/chapter1/fireworksLaunchButtonSpawner.js @@ -0,0 +1,37 @@ +// +// fireworksLaunchButtonSpawner.js +// +// Created by Eric Levin on 3/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This is the chapter 1 interface script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial) +// +// Distributed under the Apache License, Version 2.0. + + 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 SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js"); + var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx"; + var launchButton = Entities.addEntity({ + type: "Model", + name: "hifi-launch-button", + modelURL: MODEL_URL, + position: center, + dimensions: { + x: 0.98, + y: 1.16, + z: 0.98 + }, + script: SCRIPT_URL, + }) + + + function cleanup() { + Entities.deleteEntity(launchButton); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/tutorials/fireworks/chapter2/fireworksLaunchButtonEntityScript.js b/examples/tutorials/fireworks/chapter2/fireworksLaunchButtonEntityScript.js new file mode 100644 index 0000000000..66d2e96858 --- /dev/null +++ b/examples/tutorials/fireworks/chapter2/fireworksLaunchButtonEntityScript.js @@ -0,0 +1,92 @@ +// +// fireworksLaunchButtonEntityScript.js +// +// Created by Eric Levin on 3/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This is the chapter 2 entity script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial) +// +// Distributed under the Apache License, Version 2.0. + + (function() { + Script.include("../../libraries/utils.js"); + var _this; + Fireworks = function() { + _this = this; + _this.launchSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/missle+launch.wav"); + }; + + Fireworks.prototype = { + + startNearTrigger: function() { + _this.shootFirework(_this.position); + }, + + startFarTrigger: function() { + _this.shootFirework(_this.position); + }, + + clickReleaseOnEntity: function() { + _this.shootFirework(_this.position); + }, + + + + shootFirework: function(launchPosition) { + Audio.playSound(_this.launchSound, { + position: launchPosition, + volume: 0.5 + }); + + + var smoke = Entities.addEntity({ + type: "ParticleEffect", + position: _this.position, + velocity: {x: 0, y: 3, z: 0}, + lifespan: 10, + lifetime: 20, + isEmitting: true, + name: "Smoke Trail", + maxParticles: 3000, + emitRate: 80, + emitSpeed: 0, + speedSpread: 0, + polarStart: 0, + polarFinish: 0, + azimuthStart: -3.14, + azimuthFinish: 3.14, + emitAcceleration: { + x: 0, + y: 0.01, + z: 0 + }, + accelerationSpread: { + x: 0.01, + y: 0, + z: 0.01 + }, + radiusSpread: 0.03, + particleRadius: 0.3, + radiusStart: 0.06, + radiusFinish: 0.9, + alpha: 0.1, + alphaSpread: 0, + alphaStart: 0.7, + alphaFinish: 0, + textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + emitterShouldTrail: true, + }); + + }, + + preload: function(entityID) { + _this.entityID = entityID; + _this.position = Entities.getEntityProperties(_this.entityID, "position").position; + + } + + }; + + // entity scripts always need to return a newly constructed object of our type + return new Fireworks(); + }); diff --git a/examples/tutorials/fireworks/chapter2/fireworksLaunchButtonSpawner.js b/examples/tutorials/fireworks/chapter2/fireworksLaunchButtonSpawner.js new file mode 100644 index 0000000000..19fd67f6c4 --- /dev/null +++ b/examples/tutorials/fireworks/chapter2/fireworksLaunchButtonSpawner.js @@ -0,0 +1,37 @@ +// +// fireworksLaunchButtonSpawner.js +// +// Created by Eric Levin on 3/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This is the chapter 2 interface script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial) +// +// Distributed under the Apache License, Version 2.0. + + 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 SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js"); + var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx"; + var launchButton = Entities.addEntity({ + type: "Model", + name: "hifi-launch-button", + modelURL: MODEL_URL, + position: center, + dimensions: { + x: 0.98, + y: 1.16, + z: 0.98 + }, + script: SCRIPT_URL, + }) + + + function cleanup() { + Entities.deleteEntity(launchButton); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/tutorials/fireworks/chapter3/fireworksLaunchButtonEntityScript.js b/examples/tutorials/fireworks/chapter3/fireworksLaunchButtonEntityScript.js new file mode 100644 index 0000000000..f811d95315 --- /dev/null +++ b/examples/tutorials/fireworks/chapter3/fireworksLaunchButtonEntityScript.js @@ -0,0 +1,164 @@ +// +// fireworksLaunchButtonEntityScript.js +// +// Created by Eric Levin on 3/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This is the chapter 3 entity script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial) +// +// Distributed under the Apache License, Version 2.0. + + (function() { + Script.include("../../libraries/utils.js"); + var _this; + Fireworks = function() { + _this = this; + _this.launchSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/missle+launch.wav"); + _this.explosionSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/fireworksExplosion.wav"); + _this.TIME_TO_EXPLODE = 3000; + }; + + Fireworks.prototype = { + + startNearTrigger: function() { + _this.shootFirework(_this.position); + }, + + startFarTrigger: function() { + _this.shootFirework(_this.position); + }, + + clickReleaseOnEntity: function() { + _this.shootFirework(_this.position); + }, + + + + shootFirework: function(launchPosition) { + Audio.playSound(_this.launchSound, { + position: launchPosition, + volume: 0.5 + }); + + + var smoke = Entities.addEntity({ + type: "ParticleEffect", + position: _this.position, + velocity: {x: 0, y: 3, z: 0}, + linearDamping: 0, + lifespan: 10, + lifetime: 20, + isEmitting: true, + name: "Smoke Trail", + maxParticles: 3000, + emitRate: 80, + emitSpeed: 0, + speedSpread: 0, + polarStart: 0, + polarFinish: 0, + azimuthStart: -3.14, + azimuthFinish: 3.14, + emitAcceleration: { + x: 0, + y: 0.01, + z: 0 + }, + accelerationSpread: { + x: 0.01, + y: 0, + z: 0.01 + }, + radiusSpread: 0.03, + particleRadius: 0.3, + radiusStart: 0.06, + radiusFinish: 0.9, + alpha: 0.1, + alphaSpread: 0, + alphaStart: 0.7, + alphaFinish: 0, + textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + emitterShouldTrail: true, + }); + + Script.setTimeout(function() { + var explodePosition = Entities.getEntityProperties(smoke, "position").position; + _this.explodeFirework(explodePosition); + }, _this.TIME_TO_EXPLODE); + + }, + + explodeFirework: function(explodePosition) { + Audio.playSound(_this.explosionSound, { + position: explodePosition + }); + var firework = Entities.addEntity({ + name: "fireworks emitter", + position: explodePosition, + type: "ParticleEffect", + colorStart: hslToRgb({ + h: Math.random(), + s: 0.5, + l: 0.7 + }), + color: hslToRgb({ + h: Math.random(), + s: 0.5, + l: 0.5 + }), + colorFinish: hslToRgb({ + h: Math.random(), + s: 0.5, + l: 0.7 + }), + maxParticles: 10000, + lifetime: 20, + lifespan: randFloat(1.5, 3), + emitRate: randInt(500, 5000), + emitSpeed: randFloat(0.5, 2), + speedSpread: 0.2, + emitOrientation: Quat.fromPitchYawRollDegrees(randInt(0, 360), randInt(0, 360), randInt(0, 360)), + polarStart: 1, + polarFinish: randFloat(1.2, 3), + azimuthStart: -Math.PI, + azimuthFinish: Math.PI, + emitAcceleration: { + x: 0, + y: randFloat(-1, -0.2), + z: 0 + }, + accelerationSpread: { + x: Math.random(), + y: 0, + z: Math.random() + }, + particleRadius: randFloat(0.001, 0.1), + radiusSpread: Math.random() * 0.1, + radiusStart: randFloat(0.001, 0.1), + radiusFinish: randFloat(0.001, 0.1), + alpha: randFloat(0.8, 1.0), + alphaSpread: randFloat(0.1, 0.2), + alphaStart: randFloat(0.7, 1.0), + alphaFinish: randFloat(0.7, 1.0), + textures: "http://ericrius1.github.io/PlatosCave/assets/star.png", + }); + + + Script.setTimeout(function() { + Entities.editEntity(firework, { + isEmitting: false + }); + }, randInt(500, 1000)); + + }, + + preload: function(entityID) { + _this.entityID = entityID; + _this.position = Entities.getEntityProperties(_this.entityID, "position").position; + + } + + }; + + // entity scripts always need to return a newly constructed object of our type + return new Fireworks(); + }); diff --git a/examples/tutorials/fireworks/chapter3/fireworksLaunchButtonSpawner.js b/examples/tutorials/fireworks/chapter3/fireworksLaunchButtonSpawner.js new file mode 100644 index 0000000000..6b77b2609a --- /dev/null +++ b/examples/tutorials/fireworks/chapter3/fireworksLaunchButtonSpawner.js @@ -0,0 +1,37 @@ +// +// fireworksLaunchButtonSpawner.js +// +// Created by Eric Levin on 3/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This is the chapter 3 interface script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial) +// +// Distributed under the Apache License, Version 2.0. + + 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 SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js"); + var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx"; + var launchButton = Entities.addEntity({ + type: "Model", + name: "hifi-launch-button", + modelURL: MODEL_URL, + position: center, + dimensions: { + x: 0.98, + y: 1.16, + z: 0.98 + }, + script: SCRIPT_URL, + }) + + + function cleanup() { + Entities.deleteEntity(launchButton); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/utilities/tools/render/debugFramebuffer.js b/examples/utilities/tools/render/debugFramebuffer.js new file mode 100644 index 0000000000..e764cf52d8 --- /dev/null +++ b/examples/utilities/tools/render/debugFramebuffer.js @@ -0,0 +1,51 @@ +// +// ddebugFramBuffer.js +// examples/utilities/tools/render +// +// Sam Gateau created on 2/18/2016. +// 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 +// + +var DDB = Render.RenderDeferredTask.DebugDeferredBuffer; +oldConfig = DDB.toJSON(); +DDB.enabled = true; + + +// Set up the qml ui +var qml = Script.resolvePath('framebuffer.qml'); +var window = new OverlayWindow({ + title: 'Framebuffer Debug', + source: qml, + width: 400, height: 400, +}); +window.setPosition(25, 50); +window.closed.connect(function() { Script.stop(); }); + +// Debug buffer sizing +var resizing = false; + +Controller.mousePressEvent.connect(function (e) { + if (shouldStartResizing(e.x)) { + resizing = true; + } +}); +Controller.mouseReleaseEvent.connect(function() { resizing = false; }); +Controller.mouseMoveEvent.connect(function (e) { resizing && setDebugBufferSize(e.x); }); + + +function shouldStartResizing(eventX) { + var x = Math.abs(eventX - Window.innerWidth * (1.0 + DDB.size.x) / 2.0); + var mode = DDB.mode; + return mode !== -1 && x < 20; +} + +function setDebugBufferSize(x) { + x = (2.0 * (x / Window.innerWidth) - 1.0); // scale + x = Math.min(Math.max(-1, x), 1); // clamp + DDB.size = { x: x, y: -1, z: 1, w: 1 }; +} + +Script.scriptEnding.connect(function () { DDB.fromJSON(oldConfig); }); diff --git a/examples/utilities/tools/render/framebuffer.qml b/examples/utilities/tools/render/framebuffer.qml new file mode 100644 index 0000000000..d6f4ea7915 --- /dev/null +++ b/examples/utilities/tools/render/framebuffer.qml @@ -0,0 +1,52 @@ +// +// main.qml +// examples/utilities/tools/render +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Column { + spacing: 8 + Column { + id: debug + property var config: Render.getConfig("DebugDeferredBuffer") + + function setDebugMode(mode) { + debug.config.enabled = (mode != -1); + debug.config.mode = mode; + } + + Label { text: qsTr("Debug Buffer") } + ExclusiveGroup { id: bufferGroup } + Repeater { + model: [ + "Off", + "Depth", + "Albedo", + "Normal", + "Roughness", + "Metallic", + "Emissive", + "Occlusion", + "Lightmap", + "Lighting", + "Shadow", + "Pyramid Depth", + "Ambient Occlusion", + "Custom Shader" + ] + RadioButton { + text: qsTr(modelData) + exclusiveGroup: bufferGroup + checked: index == 0 + onCheckedChanged: if (checked) debug.setDebugMode(index - 1); + } + } + } +} diff --git a/examples/zones/RainyDayNightZone.json b/examples/zones/RainyDayNightZone.json new file mode 100644 index 0000000000..1efe87c9fb --- /dev/null +++ b/examples/zones/RainyDayNightZone.json @@ -0,0 +1,27 @@ +{ + "Entities": [ + { + "backgroundMode": "skybox", + "dimensions": { + "x": 10000, + "y": 10000, + "z": 10000 + }, + "name": "Rainy Day/Night Cycle", + "rotation": { + "w": 1, + "x": 0, + "y": 0, + "z": 0 + }, + "script": "https://s3.amazonaws.com/hifi-public/brad/rainstorm/lightningEntity.js", + "shapeType": "box", + "skybox": { + "url": "https://hifi-public.s3.amazonaws.com/images/SkyboxTextures/CloudyDay1.jpg" + }, + "type": "Zone", + "userData":"{\n\"ProceduralEntity\":{\n\"version\":2,\n\"shaderUrl\":\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/rainyDayNightSkybox.fs\",\n\"channels\":[\n\"https://hifi-public.s3.amazonaws.com/austin/assets/images/skybox/starmap_8k.jpg\",\n\"https://hifi-public.s3.amazonaws.com/austin/assets/images/skybox/celestial_grid.jpg\",\n\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/noise.jpg\",\n\"https://s3.amazonaws.com/hifi-public/brad/noise.jpg\"\n],\n\"uniforms\":{\n\"rotationSpeed\":0.001,\n\"uDayColor\":[0.4,0.3,0.3],\n\"constellationLevel\":0.0,\n\"constellationBoundaryLevel\":0.00,\n\"gridLevel\":0.0\n}\n},\n\"lightning\":{\n\"flashMax\":20,\n\"flashMin\":0,\n\"flashIntensityStepRandomeness\":2,\n\"flashMaxRandomness\":10,\n\"averageLightningStrikeGap\":120,\n\"extraRandomRangeLightningStrikeGap\":5,\n\"thunderURL\":\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/thunder-48k.wav\",\n\"thunderVolume\":0.1,\n\"rainURL\":\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/rain.wav\",\n\"rainVolume\":0.05\n}\n}" + } + ], + "Version": 57 +} diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index cfec3c966c..e5bdffe2e2 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -6,3 +6,17 @@ setup_hifi_project(Network) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) package_libraries_for_deployment() + +# find OpenSSL +find_package(OpenSSL REQUIRED) + +if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include") + # this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto + message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings." + "\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.") +endif () + +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 2baa7a13a7..f38923b873 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -9,14 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "IceServer.h" + +#include +#include + +#include +#include +#include +#include #include +#include #include #include -#include "IceServer.h" - const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000; const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; @@ -45,7 +52,6 @@ IceServer::IceServer(int argc, char* argv[]) : QTimer* inactivePeerTimer = new QTimer(this); connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers); inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS); - } bool IceServer::packetVersionMatch(const udt::Packet& packet) { @@ -70,9 +76,14 @@ void IceServer::processPacket(std::unique_ptr packet) { if (nlPacket->getType() == PacketType::ICEServerHeartbeat) { SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket); - - // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now - peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + if (peer) { + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + } else { + // we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation + static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied); + _serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr()); + } } else if (nlPacket->getType() == PacketType::ICEServerQuery) { QDataStream heartbeatStream(nlPacket.get()); @@ -114,31 +125,135 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) { // pull the UUID, public and private sock addrs for this peer QUuid senderUUID; HifiSockAddr publicSocket, localSocket; + QByteArray signature; QDataStream heartbeatStream(&packet); - - heartbeatStream >> senderUUID; - heartbeatStream >> publicSocket >> localSocket; + heartbeatStream >> senderUUID >> publicSocket >> localSocket; - // make sure we have this sender in our peer hash - SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); + auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos()); + heartbeatStream >> signature; - if (!matchingPeer) { - // if we don't have this sender we need to create them now - matchingPeer = QSharedPointer::create(senderUUID, publicSocket, localSocket); - _activePeers.insert(senderUUID, matchingPeer); + // make sure this is a verified heartbeat before performing any more processing + if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) { + // make sure we have this sender in our peer hash + SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); - qDebug() << "Added a new network peer" << *matchingPeer; + if (!matchingPeer) { + // if we don't have this sender we need to create them now + matchingPeer = QSharedPointer::create(senderUUID, publicSocket, localSocket); + _activePeers.insert(senderUUID, matchingPeer); + + qDebug() << "Added a new network peer" << *matchingPeer; + } else { + // we already had the peer so just potentially update their sockets + matchingPeer->setPublicSocket(publicSocket); + matchingPeer->setLocalSocket(localSocket); + } + + // update our last heard microstamp for this network peer to now + matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + + return matchingPeer; } else { - // we already had the peer so just potentially update their sockets - matchingPeer->setPublicSocket(publicSocket); - matchingPeer->setLocalSocket(localSocket); + // not verified, return the empty peer object + return SharedNetworkPeer(); + } +} + +bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) { + // check if we have a private key for this domain ID - if we do not then fire off the request for it + auto it = _domainPublicKeys.find(domainID); + if (it != _domainPublicKeys.end()) { + + // attempt to verify the signature for this heartbeat + const unsigned char* publicKeyData = reinterpret_cast(it->second.constData()); + + // first load up the public key into an RSA struct + RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size()); + + if (rsaPublicKey) { + auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); + int verificationResult = RSA_verify(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.constData()), + signature.size(), + rsaPublicKey); + + // free up the public key and remove connection token before we return + RSA_free(rsaPublicKey); + + if (verificationResult == 1) { + // this is the only success case - we return true here to indicate that the heartbeat is verified + return true; + } else { + qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API."; + } + + } else { + // we can't let this user in since we couldn't convert their public key to an RSA key we could use + qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key."; + qWarning() << "Re-requesting public key from API"; + } } - // update our last heard microstamp for this network peer to now - matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + // we could not verify this heartbeat (missing public key, could not load public key, bad actor) + // ask the metaverse API for the right public key and return false to indicate that this is not verified + requestDomainPublicKey(domainID); - return matchingPeer; + return false; +} + +void IceServer::requestDomainPublicKey(const QUuid& domainID) { + // send a request to the metaverse API for the public key for this domain + QNetworkAccessManager* manager = new QNetworkAccessManager { this }; + connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished); + + QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL }; + QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID)); + publicKeyURL.setPath(publicKeyPath); + + QNetworkRequest publicKeyRequest { publicKeyURL }; + publicKeyRequest.setAttribute(QNetworkRequest::User, domainID); + + qDebug() << "Requesting public key for domain with ID" << domainID; + + manager->get(publicKeyRequest); +} + +void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { + // get the domain ID from the QNetworkReply attribute + QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid(); + + if (reply->error() == QNetworkReply::NoError) { + // pull out the public key and store it for this domain + + // the response should be JSON + QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll()); + + static const QString DATA_KEY = "data"; + static const QString PUBLIC_KEY_KEY = "public_key"; + static const QString STATUS_KEY = "status"; + static const QString SUCCESS_VALUE = "success"; + + auto responseObject = responseDocument.object(); + if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) { + auto dataObject = responseObject[DATA_KEY].toObject(); + if (dataObject.contains(PUBLIC_KEY_KEY)) { + _domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8()); + } else { + qWarning() << "There was no public key present in response for domain with ID" << domainID; + } + } else { + qWarning() << "The metaverse API did not return success for public key request for domain with ID" << domainID; + } + + } else { + // there was a problem getting the public key for the domain + // log it since it will be re-requested on the next heartbeat + + qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString(); + } } void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) { diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index f1c2c06b65..81234b2c3c 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -16,13 +16,15 @@ #include #include +#include + #include #include #include #include #include -typedef QHash NetworkPeerHash; +class QNetworkReply; class IceServer : public QCoreApplication, public HTTPRequestHandler { Q_OBJECT @@ -31,6 +33,7 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); private slots: void clearInactivePeers(); + void publicKeyReplyFinished(QNetworkReply* reply); private: bool packetVersionMatch(const udt::Packet& packet); void processPacket(std::unique_ptr packet); @@ -38,10 +41,19 @@ private: SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket); void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr); + bool isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature); + void requestDomainPublicKey(const QUuid& domainID); + QUuid _id; udt::Socket _serverSocket; + + using NetworkPeerHash = QHash; NetworkPeerHash _activePeers; + HTTPManager _httpManager; + + using DomainPublicKeyHash = std::unordered_map; + DomainPublicKeyHash _domainPublicKeys; }; #endif // hifi_IceServer_h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b398778db7..bc4a53c797 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -268,7 +268,7 @@ if (WIN32) set(TARGET_INSTALL_DIR ${INTERFACE_INSTALL_DIR}) set(TARGET_INSTALL_COMPONENT ${CLIENT_COMPONENT}) - manually_install_ssl_eay() + manually_install_openssl_for_qt() package_libraries_for_deployment() endif() diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 54a23dce07..3e8e5e5b90 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index 9c7aee722b..5daec8eb36 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -18,7 +18,7 @@ Original.Button { id: button property int color: 0 width: 120 - height: 30 + height: 28 style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml new file mode 100644 index 0000000000..bd16a33776 --- /dev/null +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -0,0 +1,70 @@ +// +// CheckBox.qml +// +// Created by David Rowe on 26 Feb 2016 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +Original.CheckBox { + id: checkBox + HifiConstants { id: hifi } + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + readonly property int boxSize: 14 + readonly property int boxRadius: 3 + readonly property int checkSize: 10 + readonly property int checkRadius: 2 + + style: CheckBoxStyle { + indicator: Rectangle { + id: box + width: boxSize + height: boxSize + radius: boxRadius + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: checkRadius + anchors.centerIn: parent + color: hifi.colors.checkboxChecked + border.width: 1 + border.color: hifi.colors.checkboxCheckedBorder + visible: checked && !pressed || !checked && pressed + } + } + + label: Label { + text: control.text + colorScheme: checkBox.colorScheme + x: checkBox.boxSize / 2 + wrapMode: Text.Wrap + } + } +} diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml new file mode 100644 index 0000000000..392d5534c8 --- /dev/null +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -0,0 +1,202 @@ +// +// ComboBox.qml +// +// Created by Bradley Austin David on 27 Jan 2016 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls +import "." as VrControls + +FocusScope { + id: root + + property alias model: comboBox.model; + property alias comboBox: comboBox + readonly property alias currentText: comboBox.currentText; + property alias currentIndex: comboBox.currentIndex; + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (comboBoxLabel.visible ? comboBoxLabel.height + comboBoxLabel.anchors.bottomMargin : 0) + + readonly property ComboBox control: comboBox + + implicitHeight: comboBox.height; + focus: true + + Rectangle { + id: background + gradient: Gradient { + GradientStop { + position: 0.2 + color: popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart) + } + GradientStop { + position: 1.0 + color: popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish) + } + } + anchors.fill: parent + } + + SystemPalette { id: palette } + + ComboBox { + id: comboBox + anchors.fill: parent + visible: false + height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. + } + + FiraSansSemiBold { + id: textField + anchors { + left: parent.left + leftMargin: hifi.dimensions.textPadding + right: dropIcon.left + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.textFieldInput + text: comboBox.currentText + elide: Text.ElideRight + color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) + } + + Item { + id: dropIcon + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + height: background.height + width: height + Rectangle { + width: 1 + height: parent.height + anchors.top: parent.top + anchors.left: parent.left + color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray + } + HiFiGlyphs { + anchors { + top: parent.top + topMargin: -8 + horizontalCenter: parent.horizontalCenter + } + size: hifi.dimensions.spinnerSize + text: hifi.glyphs.caratDn + color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText) + } + } + + MouseArea { + id: controlHover + hoverEnabled: true + anchors.fill: parent + onClicked: toggleList(); + } + + function toggleList() { + if (popup.visible) { + hideList(); + } else { + showList(); + } + } + + function showList() { + var r = desktop.mapFromItem(root, 0, 0, root.width, root.height); + listView.currentIndex = root.currentIndex + scrollView.x = r.x; + scrollView.y = r.y + r.height; + var bottom = scrollView.y + scrollView.height; + if (bottom > desktop.height) { + scrollView.y -= bottom - desktop.height + 8; + } + popup.visible = true; + popup.forceActiveFocus(); + } + + function hideList() { + popup.visible = false; + } + + FocusScope { + id: popup + parent: desktop + anchors.fill: parent + z: desktop.zLevels.menu + visible: false + focus: true + + MouseArea { + anchors.fill: parent + onClicked: hideList(); + } + + function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; } + function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; } + function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); } + function selectSpecificItem(index) { root.currentIndex = index; hideList(); } + + Keys.onUpPressed: previousItem(); + Keys.onDownPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + Keys.onEscapePressed: hideList(); + + ScrollView { + id: scrollView + height: 480 + + ListView { + id: listView + height: textField.height * count * 1.4 + model: root.model + delegate: Rectangle { + width: root.width + 4 + height: popupText.implicitHeight * 1.4 + color: popupHover.containsMouse ? hifi.colors.primaryHighlight : (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + FiraSansSemiBold { + anchors.left: parent.left + anchors.leftMargin: hifi.dimensions.textPadding + anchors.verticalCenter: parent.verticalCenter + id: popupText + text: listView.model[index] + size: hifi.fontSizes.textFieldInput + color: hifi.colors.baseGray + } + MouseArea { + id: popupHover + anchors.fill: parent; + hoverEnabled: true + onEntered: listView.currentIndex = index; + onClicked: popup.selectSpecificItem(index) + } + } + } + } + } + + HifiControls.Label { + id: comboBoxLabel + text: root.label + colorScheme: root.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 4 + visible: label != "" + } +} diff --git a/interface/resources/qml/controls-uit/ContentSection.qml b/interface/resources/qml/controls-uit/ContentSection.qml new file mode 100644 index 0000000000..d51ab9cd1b --- /dev/null +++ b/interface/resources/qml/controls-uit/ContentSection.qml @@ -0,0 +1,133 @@ +// +// ContentSection.qml +// +// Created by David Rowe on 16 Feb 2016 +// 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../styles-uit" + +Column { + property string name: "Static Section" + property bool isFirst: false + property bool isCollapsible: false // Set at creation. + property bool isCollapsed: false + + spacing: 0 // Defer spacing decisions to individual controls. + + anchors { + left: parent.left + leftMargin: hifi.dimensions.contentMargin.x + right: parent.right + rightMargin: hifi.dimensions.contentMargin.x + } + + function toggleCollapsed() { + if (isCollapsible) { + isCollapsed = !isCollapsed; + for (var i = 1; i < children.length; i++) { + children[i].visible = !isCollapsed; + } + } + } + + Item { + id: sectionName + anchors.left: parent.left + anchors.right: parent.right + height: leadingSpace.height + topBar.height + heading.height + bottomBar.height + + Item { + id: leadingSpace + width: 1 + height: isFirst ? hifi.dimensions.contentSpacing.y : hifi.dimensions.controlInterlineHeight + anchors.top: parent.top + } + + Item { + id: topBar + visible: !isFirst + height: visible ? 2 : 0 + anchors.top: leadingSpace.bottom + + Rectangle { + id: shadow + width: frame.width + height: 1 + color: hifi.colors.baseGrayShadow + x: -hifi.dimensions.contentMargin.x + } + + Rectangle { + width: frame.width + height: 1 + color: hifi.colors.baseGrayHighlight + x: -hifi.dimensions.contentMargin.x + anchors.top: shadow.bottom + } + } + + Item { + id: heading + anchors { + left: parent.left + right: parent.right + top: topBar.bottom + } + height: (isCollapsible ? 3 : 2) * hifi.dimensions.contentSpacing.y + + RalewayRegular { + id: title + anchors { + left: parent.left + top: parent.top + topMargin: hifi.dimensions.contentSpacing.y + } + size: hifi.fontSizes.sectionName + font.capitalization: Font.AllUppercase + text: name + color: hifi.colors.lightGrayText + } + + HiFiGlyphs { + anchors { + verticalCenter: title.verticalCenter + right: parent.right + rightMargin: -4 + } + y: -2 + size: hifi.fontSizes.disclosureButton + text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse + color: hifi.colors.lightGrayText + visible: isCollapsible + } + + MouseArea { + anchors.fill: parent + onClicked: toggleCollapsed() + } + } + + LinearGradient { + id: bottomBar + visible: isCollapsible + width: frame.width + height: visible ? 4 : 0 + x: -hifi.dimensions.contentMargin.x + anchors.top: heading.bottom + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background. + } + cached: true + } + } +} diff --git a/interface/resources/qml/controls-uit/Label.qml b/interface/resources/qml/controls-uit/Label.qml new file mode 100644 index 0000000000..f4015edd62 --- /dev/null +++ b/interface/resources/qml/controls-uit/Label.qml @@ -0,0 +1,20 @@ +// +// Label.qml +// +// Created by David Rowe on 26 Feb 2016 +// 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 +// + +import QtQuick 2.5 + +import "../styles-uit" + +RalewaySemibold { + property int colorScheme: hifi.colorSchemes.light + + size: hifi.fontSizes.inputLabel + color: colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGray : hifi.colors.lightGrayText +} diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml new file mode 100644 index 0000000000..cf59e1d989 --- /dev/null +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -0,0 +1,98 @@ +// +// Slider.qml +// +// Created by David Rowe on 27 Feb 2016 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Slider { + id: slider + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0) + + height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. + y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 + + style: SliderStyle { + + groove: Rectangle { + implicitWidth: 50 + implicitHeight: hifi.dimensions.sliderGrooveHeight + radius: height / 2 + color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark + + Rectangle { + width: parent.height - 2 + height: slider.value * slider.width - 1 + radius: height / 2 + anchors { + top: parent.top + topMargin: width + 1 + left: parent.left + leftMargin: 1 + } + transformOrigin: Item.TopLeft + rotation: -90 + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.blueAccent } + GradientStop { position: 1.0; color: hifi.colors.primaryHighlight } + } + } + } + + handle: Rectangle { + implicitWidth: hifi.dimensions.sliderHandleSize + implicitHeight: hifi.dimensions.sliderHandleSize + radius: height / 2 + border.width: 1 + border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark + gradient: Gradient { + GradientStop { + position: 0.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart ) + : (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart ) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish ) + : (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish ) + } + } + + Rectangle { + height: parent.height - 2 + width: height + radius: height / 2 + anchors.centerIn: parent + color: hifi.colors.transparent + border.width: 1 + border.color: hifi.colors.black + } + } + } + + HifiControls.Label { + id: sliderLabel + text: slider.label + colorScheme: slider.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 2 + visible: label != "" + } +} diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml new file mode 100644 index 0000000000..f603a584b3 --- /dev/null +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -0,0 +1,79 @@ +// +// SpinBox.qml +// +// Created by David Rowe on 26 Feb 2016 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +SpinBox { + id: spinBox + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) + + FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + font.family: firaSansSemiBold.name + font.pixelSize: hifi.fontSizes.textFieldInput + height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. + + y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0 + + style: SpinBoxStyle { + background: Rectangle { + color: isLightColorScheme + ? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray) + : (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow) + border.color: hifi.colors.primaryHighlight + border.width: spinBox.focus ? 1 : 0 + } + + textColor: isLightColorScheme + ? (spinBox.focus ? hifi.colors.black : hifi.colors.lightGray) + : (spinBox.focus ? hifi.colors.white : hifi.colors.lightGrayText) + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + + horizontalAlignment: Qt.AlignLeft + padding.left: hifi.dimensions.textPadding + padding.right: hifi.dimensions.spinnerSize + + incrementControl: HiFiGlyphs { + id: incrementButton + text: hifi.glyphs.caratUp + x: 6 + y: 2 + size: hifi.dimensions.spinnerSize + color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } + + decrementControl: HiFiGlyphs { + text: hifi.glyphs.caratDn + x: 6 + y: -3 + size: hifi.dimensions.spinnerSize + color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } + } + + HifiControls.Label { + id: spinBoxLabel + text: spinBox.label + colorScheme: spinBox.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 4 + visible: label != "" + } +} diff --git a/interface/resources/qml/controls-uit/StaticSection.qml b/interface/resources/qml/controls-uit/StaticSection.qml deleted file mode 100644 index a4857635f1..0000000000 --- a/interface/resources/qml/controls-uit/StaticSection.qml +++ /dev/null @@ -1,65 +0,0 @@ -// -// StaticSection.qml -// -// Created by David Rowe on 16 Feb 2016 -// 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 -// - -import QtQuick 2.5 - -import "../styles-uit" - -Column { - property string name: "Static Section" - property bool hasSeparator: false - - spacing: hifi.dimensions.contentSpacing.y - - anchors { - left: parent.left - leftMargin: hifi.dimensions.contentMargin.x - right: parent.right - rightMargin: hifi.dimensions.contentMargin.x - } - - VerticalSpacer { } - - Item { - visible: hasSeparator - anchors.top: sectionName.top - - Rectangle { - width: frame.width - height: 1 - color: hifi.colors.baseGrayShadow - x: -hifi.dimensions.contentMargin.x - anchors.bottom: highlight.top - } - - Rectangle { - id: highlight - width: frame.width - height: 1 - color: hifi.colors.baseGrayHighlight - x: -hifi.dimensions.contentMargin.x - anchors.bottom: parent.top - } - } - - RalewayRegular { - id: sectionName - text: parent.name - size: hifi.fontSizes.sectionName - font.capitalization: Font.AllUppercase - color: hifi.colors.lightGrayText - verticalAlignment: Text.AlignBottom - height: { - if (hasSeparator) { - hifi.dimensions.contentMargin.y - } - } - } -} diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index fc6c310612..de6950c07e 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -133,13 +133,14 @@ TableView { HiFiGlyphs { id: reloadButton text: hifi.glyphs.reloadSmall - color: parent.color + color: reloadButtonArea.pressed ? hifi.colors.white : parent.color anchors { top: parent.top right: stopButton.left verticalCenter: parent.verticalCenter } MouseArea { + id: reloadButtonArea anchors { fill: parent; margins: -2 } onClicked: reloadScript(model.url) } @@ -149,13 +150,14 @@ TableView { HiFiGlyphs { id: stopButton text: hifi.glyphs.closeSmall - color: parent.color + color: stopButtonArea.pressed ? hifi.colors.white : parent.color anchors { top: parent.top right: parent.right verticalCenter: parent.verticalCenter } MouseArea { + id: stopButtonArea anchors { fill: parent; margins: -2 } onClicked: stopScript(model.url) } diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index fa1f7c97ad..ff1bbe8eb6 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -13,25 +13,31 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import "../styles-uit" +import "../controls-uit" as HifiControls TextField { id: textField property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light property string label: "" + property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height : 0) + + placeholderText: textField.placeholderText FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } font.family: firaSansSemiBold.name font.pixelSize: hifi.fontSizes.textFieldInput height: implicitHeight + 4 // Make surrounding box higher so that highlight is vertically centered. - placeholderText: textField.label // Instead of separate label (see below). + + y: textFieldLabel.visible ? textFieldLabel.height + textFieldLabel.anchors.bottomMargin : 0 style: TextFieldStyle { - textColor: textField.colorScheme == hifi.colorSchemes.light + textColor: isLightColorScheme ? (textField.focus ? hifi.colors.black : hifi.colors.lightGray) : (textField.focus ? hifi.colors.white : hifi.colors.lightGrayText) background: Rectangle { - color: textField.colorScheme == hifi.colorSchemes.light + color: isLightColorScheme ? (textField.focus ? hifi.colors.white : hifi.colors.lightGray) : (textField.focus ? hifi.colors.black : hifi.colors.baseGrayShadow) border.color: hifi.colors.primaryHighlight @@ -44,16 +50,13 @@ TextField { padding.right: hifi.dimensions.textPadding } - /* - // Separate label instead of placeholderText. - RalewaySemibold { + HifiControls.Label { + id: textFieldLabel text: textField.label - size: hifi.fontSizes.inputLabel - color: hifi.colors.lightGrayText + colorScheme: textField.colorScheme anchors.left: parent.left anchors.bottom: parent.top anchors.bottomMargin: 4 visible: label != "" } - */ } diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index d100cdcd44..18741d6420 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -55,11 +55,11 @@ TreeView { alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd branchDelegate: HiFiGlyphs { - text: styleData.isExpanded ? hifi.glyphs.disclosureCollapse : hifi.glyphs.disclosureExpand - size: hifi.fontSizes.tableText * 2.5 // tableText is in points; proportionately scale to pixels + text: styleData.isExpanded ? hifi.glyphs.caratDn : hifi.glyphs.caratR + size: hifi.fontSizes.carat color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) anchors { left: parent ? parent.left : undefined leftMargin: hifi.dimensions.tablePadding / 2 diff --git a/interface/resources/qml/controls-uit/VerticalSpacer.qml b/interface/resources/qml/controls-uit/VerticalSpacer.qml index a3a0276f8f..6fc49605c0 100644 --- a/interface/resources/qml/controls-uit/VerticalSpacer.qml +++ b/interface/resources/qml/controls-uit/VerticalSpacer.qml @@ -14,5 +14,5 @@ import "../styles-uit" Item { width: 1 // Must be non-zero - height: hifi.dimensions.contentSpacing.y + height: hifi.dimensions.controlInterlineHeight } diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 3b594153dc..40cc713397 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -1,10 +1,19 @@ +// +// PreferencesDialog.qml +// +// Created by Bradley Austin Davis on 24 Jan 2016 +// 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 +// + import QtQuick 2.5 import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs -import Qt.labs.settings 1.0 -import "../controls" as HifiControls -import "../windows" +import "../controls-uit" as HifiControls +import "../styles-uit" +import "../windows-uit" import "preferences" Window { @@ -16,6 +25,9 @@ Window { height: 577 property var sections: [] property var showCategories: [] + minSize: Qt.vector2d(400, 500) + + HifiConstants { id: hifi } function saveAll() { for (var i = 0; i < sections.length; ++i) { @@ -33,10 +45,8 @@ Window { destroy(); } - Rectangle { - anchors.fill: parent - clip: true - color: "white" + Column { + width: pane.contentWidth Component { id: sectionBuilder @@ -64,45 +74,45 @@ Window { } if (sections.length) { - sections[0].expanded = true; + // Default sections to expanded/collapsed as appropriate for dialog. if (sections.length === 1) { sections[0].collapsable = false + sections[0].expanded = true + } else { + for (i = 0; i < sections.length; i++) { + sections[i].collapsable = true; + sections[i].expanded = true; + } } + sections[0].isFirst = true; + sections[sections.length - 1].isLast = true; } } - Flickable { - id: flickable - clip: true - interactive: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: dialogButtons.top - anchors.bottomMargin: 8 - contentHeight: prefControls.height - contentWidth: parent.width - - Column { - id: prefControls - anchors.left: parent.left - anchors.right: parent.right - } + Column { + id: prefControls + width: pane.contentWidth } - Row { - id: dialogButtons - anchors { bottom: parent.bottom; right: parent.right; margins: 8 } + } - Button { - text: "Cancel"; - onClicked: root.restoreAll(); - } + footer: Row { + anchors { + right: parent.right; + rightMargin: hifi.dimensions.contentMargin.x + verticalCenter: parent.verticalCenter + } + spacing: hifi.dimensions.contentSpacing.x - Button { - text: "Save all changes" - onClicked: root.saveAll(); - } + HifiControls.Button { + text: "Save changes" + color: hifi.buttons.blue + onClicked: root.saveAll() + } + + HifiControls.Button { + text: "Cancel" + color: hifi.buttons.white + onClicked: root.restoreAll() } } } - diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index a92392799d..8f05ca4ffe 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -1,15 +1,25 @@ +// +// AvatarPreference.qml +// +// Created by Bradley Austin Davis on 22 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 + +import "../../dialogs" +import "../../controls-uit" Preference { id: root - property alias buttonText: button.text property alias text: dataTextField.text + property alias buttonText: button.text property alias placeholderText: dataTextField.placeholderText - property real spacing: 8 property var browser; - height: labelText.height + Math.max(dataTextField.height, button.height) + spacing + height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { dataTextField.text = preference.value; @@ -41,40 +51,48 @@ Preference { preference.save(); } - Text { - id: labelText - color: enabled ? "black" : "gray" - text: root.label - } - - TextField { - id: dataTextField - placeholderText: root.placeholderText - text: preference.value - style: TextFieldStyle { renderType: Text.QtRendering } + Item { + id: control anchors { - top: labelText.bottom left: parent.left - right: button.left - topMargin: root.spacing - rightMargin: root.spacing + right: parent.right + bottom: parent.bottom } - } + height: Math.max(dataTextField.controlHeight, button.height) - Component { - id: avatarBrowserBuilder; - AvatarBrowser { } - } - - Button { - id: button - anchors { right: parent.right; verticalCenter: dataTextField.verticalCenter } - text: "Browse" - onClicked: { - root.browser = avatarBrowserBuilder.createObject(desktop); - root.browser.windowDestroyed.connect(function(){ - root.browser = null; - }) + TextField { + id: dataTextField + placeholderText: root.placeholderText + text: preference.value + label: root.label + anchors { + left: parent.left + right: button.left + rightMargin: hifi.dimensions.contentSpacing.x + bottom: parent.bottom + } + colorScheme: hifi.colorSchemes.dark } + + Component { + id: avatarBrowserBuilder; + AvatarBrowser { } + } + + Button { + id: button + text: "Browse" + anchors { + right: parent.right + verticalCenter: dataTextField.verticalCenter + } + onClicked: { + root.browser = avatarBrowserBuilder.createObject(desktop); + root.browser.windowDestroyed.connect(function(){ + root.browser = null; + }) + } + } + } } diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 790d1d82ea..9a19889938 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -1,16 +1,23 @@ +// +// BrowsablePreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "../../dialogs" +import "../../controls-uit" Preference { id: root - property alias buttonText: button.text property alias text: dataTextField.text property alias placeholderText: dataTextField.placeholderText - property real spacing: 8 - height: labelText.height + Math.max(dataTextField.height, button.height) + spacing + height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { dataTextField.text = preference.value; @@ -21,42 +28,49 @@ Preference { preference.save(); } - Text { - id: labelText - color: enabled ? "black" : "gray" - text: root.label - } - - TextField { - id: dataTextField - placeholderText: root.placeholderText - text: preference.value - style: TextFieldStyle { renderType: Text.QtRendering } + Item { + id: control anchors { - top: labelText.bottom left: parent.left - right: button.left - topMargin: root.spacing - rightMargin: root.spacing + right: parent.right + bottom: parent.bottom } - } + height: Math.max(dataTextField.controlHeight, button.height) - Component { - id: fileBrowserBuilder; - FileDialog { selectDirectory: true } - } + TextField { + id: dataTextField - Button { - id: button - anchors { right: parent.right; verticalCenter: dataTextField.verticalCenter } - text: preference.browseLabel - onClicked: { - var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) }); - browser.selectedFile.connect(function(fileUrl){ - console.log(fileUrl); - dataTextField.text = fileDialogHelper.urlToPath(fileUrl); - }); + anchors { + left: parent.left + right: button.left + rightMargin: hifi.dimensions.contentSpacing.x + bottom: parent.bottom + } + + label: root.label + placeholderText: root.placeholderText + colorScheme: hifi.colorSchemes.dark } + Component { + id: fileBrowserBuilder; + FileDialog { selectDirectory: true } + } + + Button { + id: button + text: preference.browseLabel + anchors { + right: parent.right + verticalCenter: dataTextField.verticalCenter + } + onClicked: { + var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) }); + browser.selectedFile.connect(function(fileUrl){ + console.log(fileUrl); + dataTextField.text = fileDialogHelper.urlToPath(fileUrl); + }); + } + } } } diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index b0c2846117..06332bd1be 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -1,16 +1,29 @@ +// +// ButtonPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original + +import "../../controls-uit" Preference { id: root - height: button.height + height: button.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: button.text = preference.name; function save() { } - Original.Button { + Button { id: button onClicked: preference.trigger() + width: 180 + anchors.bottom: parent.bottom } } diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index f28f3ab90b..f8f992735c 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -1,10 +1,20 @@ +// +// CheckBoxPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "../../controls" + +import "../../controls-uit" Preference { id: root - height: checkBox.implicitHeight + height: spacer.height + Math.max(hifi.dimensions.controlLineHeight, checkBox.implicitHeight) Component.onCompleted: { checkBox.checked = preference.value; @@ -16,9 +26,25 @@ Preference { preference.save(); } + Item { + id: spacer + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: isFirstCheckBox ? hifi.dimensions.controlInterlineHeight : 0 + } + CheckBox { id: checkBox - anchors.fill: parent + anchors { + top: spacer.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } text: root.label + colorScheme: hifi.colorSchemes.dark } } diff --git a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml index 9f323ace63..860cbcb5a8 100644 --- a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml @@ -1,14 +1,26 @@ +// +// ComboBoxPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 + import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 +import "../../controls-uit" as HiFiControls +import "../../styles-uit" Preference { id: root - property real spacing: 8 - height: labelText.height + dataComboBox.height + spacing + height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { - dataComboBox.currentIndex = dataComboBox.find(preference.value); + dataComboBox.currentIndex = dataComboBox.comboBox.find(preference.value); } function save() { @@ -16,22 +28,38 @@ Preference { preference.save(); } - Text { - id: labelText - color: enabled ? "black" : "gray" - text: root.label - } - - ComboBox { - id: dataComboBox - model: preference.items - style: ComboBoxStyle { renderType: Text.QtRendering } + Item { + id: control anchors { - top: labelText.bottom left: parent.left right: parent.right - topMargin: root.spacing - rightMargin: root.spacing + bottom: parent.bottom + } + height: Math.max(labelText.height, dataComboBox.controlHeight) + + HiFiControls.Label { + id: labelText + text: root.label + ":" + colorScheme: hifi.colorSchemes.dark + anchors { + left: parent.left + right: dataComboBox.left + rightMargin: hifi.dimensions.labelPadding + verticalCenter: parent.verticalCenter + } + horizontalAlignment: Text.AlignRight + wrapMode: Text.Wrap + } + + HiFiControls.ComboBox { + id: dataComboBox + model: preference.items + width: 150 + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + colorScheme: hifi.colorSchemes.dark } } } diff --git a/interface/resources/qml/dialogs/preferences/EditablePreference.qml b/interface/resources/qml/dialogs/preferences/EditablePreference.qml index ddd5600bca..8acf8e1f76 100644 --- a/interface/resources/qml/dialogs/preferences/EditablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/EditablePreference.qml @@ -1,11 +1,21 @@ +// +// EditablePreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 + +import "../../dialogs" +import "../../controls-uit" Preference { id: root - property real spacing: 8 - height: labelText.height + dataTextField.height + spacing + height: dataTextField.controlHeight + hifi.dimensions.controlInterlineHeight Component.onCompleted: { dataTextField.text = preference.value; @@ -16,22 +26,16 @@ Preference { preference.save(); } - Text { - id: labelText - color: enabled ? "black" : "gray" - text: root.label - } - TextField { id: dataTextField placeholderText: preference.placeholderText - style: TextFieldStyle { renderType: Text.QtRendering } + label: root.label + colorScheme: hifi.colorSchemes.dark + anchors { - top: labelText.bottom left: parent.left right: parent.right - topMargin: root.spacing - rightMargin: root.spacing + bottom: parent.bottom } } } diff --git a/interface/resources/qml/dialogs/preferences/Preference.qml b/interface/resources/qml/dialogs/preferences/Preference.qml index b119bcfcd1..1d72197382 100644 --- a/interface/resources/qml/dialogs/preferences/Preference.qml +++ b/interface/resources/qml/dialogs/preferences/Preference.qml @@ -1,3 +1,13 @@ +// +// Preference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 import QtQuick.Controls 1.4 @@ -6,6 +16,7 @@ Item { anchors { left: parent.left; right: parent.right } property var preference; property string label: preference ? preference.name : ""; + property bool isFirstCheckBox; Component.onCompleted: { if (preference) { preference.load(); diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 63aa19651a..e48612ca03 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -1,20 +1,33 @@ +// +// Section.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 import QtQuick.Controls 1.4 import Hifi 1.0 -import "../../controls" as VrControls +import "../../controls-uit" as HiFiControls +import "../../styles-uit" import "." Preference { id: root property bool collapsable: true property bool expanded: false + property bool isFirst: false + property bool isLast: false property string name: "Header" property real spacing: 8 - readonly property alias toggle: toggle - readonly property alias header: header default property alias preferences: contentContainer.children + HifiConstants { id: hifi } + function saveAll() { for (var i = 0; i < d.preferences.length; ++i) { var preference = d.preferences[i]; @@ -29,47 +42,24 @@ Preference { } } - clip: true - children: [ toggle, header, contentContainer ] - height: expanded ? header.height + contentContainer.height + root.spacing * 3 - : Math.max(toggle.height, header.height) + root.spacing * 2 - Behavior on height { PropertyAnimation {} } + children: [ contentContainer ] + + height: contentContainer.height + (root.isLast ? 2 * hifi.dimensions.contentSpacing.y : 0) Component.onCompleted: d.buildPreferences(); - function toggleExpanded() { - root.expanded = !root.expanded; - } - - VrControls.FontAwesome { - id: toggle - width: root.collapsable ? height : 0 - anchors { left: parent.left; top: parent.top; margins: root.spacing } - visible: root.collapsable - enabled: root.collapsable - rotation: root.expanded ? 0 : -90 - text: "\uf078" - Behavior on rotation { PropertyAnimation {} } - MouseArea { anchors.fill: parent; onClicked: root.toggleExpanded() } - } - - Text { - id: header - anchors { left: toggle.right; top: parent.top; leftMargin: root.spacing * 2; margins: root.spacing } - font.bold: true - font.pointSize: 16 - color: "#0e7077" - text: root.name - MouseArea { anchors.fill: parent; onClicked: root.toggleExpanded() } - } - - Column { + HiFiControls.ContentSection { id: contentContainer - spacing: root.spacing - anchors { left: toggle.right; top: header.bottom; topMargin: root.spacing; right: parent.right; margins: root.spacing } - enabled: root.expanded - visible: root.expanded - clip: true + name: root.name + isFirst: root.isFirst + isCollapsible: root.collapsable + isCollapsed: !root.expanded + + anchors { + left: parent.left + right: parent.right + margins: 0 + } } QtObject { @@ -83,6 +73,7 @@ Preference { property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } property var preferences: [] + property int checkBoxCount: 0 function buildPreferences() { var categoryPreferences = Preferences.preferencesByCategory[root.name]; @@ -99,40 +90,49 @@ Preference { var builder; switch (preference.type) { case Preference.Editable: + checkBoxCount = 0; builder = editableBuilder; break; case Preference.Browsable: + checkBoxCount = 0; builder = browsableBuilder; break; case Preference.Spinner: + checkBoxCount = 0; builder = spinnerBuilder; break; case Preference.Slider: + checkBoxCount = 0; builder = sliderBuilder; break; case Preference.Checkbox: + checkBoxCount++; + console.log("####### checkBoxCount = " + checkBoxCount); builder = checkboxBuilder; break; case Preference.Avatar: + checkBoxCount = 0; builder = avatarBuilder; break; case Preference.Button: + checkBoxCount = 0; builder = buttonBuilder; break; case Preference.ComboBox: + checkBoxCount = 0; builder = comboBoxBuilder; break; }; if (builder) { - preferences.push(builder.createObject(contentContainer, { preference: preference })); + preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) })); } } } diff --git a/interface/resources/qml/dialogs/preferences/SliderPreference.qml b/interface/resources/qml/dialogs/preferences/SliderPreference.qml index 0865b49dd0..e9013bc17a 100644 --- a/interface/resources/qml/dialogs/preferences/SliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SliderPreference.qml @@ -1,10 +1,22 @@ +// +// SpinBoxPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 + +import "../../dialogs" +import "../../controls-uit" Preference { id: root property alias slider: slider - height: slider.height + height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { slider.value = preference.value; @@ -15,16 +27,38 @@ Preference { preference.save(); } - Text { - text: root.label - color: enabled ? "black" : "gray" - anchors.verticalCenter: slider.verticalCenter - } + Item { + id: control + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: Math.max(labelText.height, slider.height) - Slider { - id: slider - value: preference.value - width: 130 - anchors { right: parent.right } + Label { + id: labelText + text: root.label + ":" + colorScheme: hifi.colorSchemes.dark + anchors { + left: parent.left + right: slider.left + rightMargin: hifi.dimensions.labelPadding + verticalCenter: parent.verticalCenter + } + horizontalAlignment: Text.AlignRight + wrapMode: Text.Wrap + } + + Slider { + id: slider + value: preference.value + width: 130 + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + colorScheme: hifi.colorSchemes.dark + } } } diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index 513ae25974..e670cd37c4 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -1,10 +1,21 @@ +// +// SpinBoxPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// 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 +// + import QtQuick 2.5 -import QtQuick.Controls 1.4 + +import "../../controls-uit" Preference { id: root property alias spinner: spinner - height: spinner.height + height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { spinner.value = preference.value; @@ -15,18 +26,40 @@ Preference { preference.save(); } - Text { - text: root.label - color: root.enabled ? "black" : "gray" - anchors.verticalCenter: spinner.verticalCenter - } + Item { + id: control + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: Math.max(spinnerLabel.height, spinner.controlHeight) - SpinBox { - id: spinner - decimals: preference.decimals - minimumValue: preference.min - maximumValue: preference.max - width: 100 - anchors { right: parent.right } + Label { + id: spinnerLabel + text: root.label + ":" + colorScheme: hifi.colorSchemes.dark + anchors { + left: parent.left + right: spinner.left + rightMargin: hifi.dimensions.labelPadding + verticalCenter: parent.verticalCenter + } + horizontalAlignment: Text.AlignRight + wrapMode: Text.Wrap + } + + SpinBox { + id: spinner + decimals: preference.decimals + minimumValue: preference.min + maximumValue: preference.max + width: 100 + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + colorScheme: hifi.colorSchemes.dark + } } } diff --git a/interface/resources/qml/hifi/dialogs/AudioPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/AudioPreferencesDialog.qml index 09b4e52a14..0c7c881bbf 100644 --- a/interface/resources/qml/hifi/dialogs/AudioPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AudioPreferencesDialog.qml @@ -6,7 +6,7 @@ import "../../dialogs" PreferencesDialog { id: root objectName: "AudioPreferencesDialog" - title: "Audio Preferences" + title: "Audio Settings" showCategories: ["Audio"] property var settings: Settings { category: root.objectName diff --git a/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml index bc5d827ead..86f195612c 100644 --- a/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml @@ -6,7 +6,7 @@ import "../../dialogs" PreferencesDialog { id: root objectName: "AvatarPreferencesDialog" - title: "Avatar Preferences" + title: "Avatar Settings" showCategories: [ "Avatar Basics", "Avatar Tuning", "Avatar Camera" ] property var settings: Settings { category: root.objectName diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml index 81e4924204..95f55f504b 100644 --- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml @@ -1,3 +1,13 @@ +// +// PreferencesDialog.qml +// +// Created by Bradley Austin Davis on 24 Jan 2016 +// 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 +// + import QtQuick 2.5 import Qt.labs.settings 1.0 @@ -6,7 +16,7 @@ import "../../dialogs" PreferencesDialog { id: root objectName: "GeneralPreferencesDialog" - title: "General Preferences" + title: "General Settings" showCategories: ["Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers"] property var settings: Settings { category: root.objectName diff --git a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml index dca3a8694c..d95bafd0a9 100644 --- a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml @@ -6,7 +6,7 @@ import "../../dialogs" PreferencesDialog { id: root objectName: "GraphicsPreferencesDialog" - title: "Graphics Preferences" + title: "Graphics Settings" showCategories: ["Graphics"] property var settings: Settings { category: root.objectName diff --git a/interface/resources/qml/hifi/dialogs/LodPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/LodPreferencesDialog.qml index 1c9016bcfa..43f9731efd 100644 --- a/interface/resources/qml/hifi/dialogs/LodPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/LodPreferencesDialog.qml @@ -6,7 +6,7 @@ import "../../dialogs" PreferencesDialog { id: root objectName: "LodPreferencesDialog" - title: "Level of Detail preferences" + title: "LOD Settings" showCategories: ["Level of Detail Tuning"] property var settings: Settings { category: root.objectName diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 55b8946805..b156a95a02 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -24,7 +24,7 @@ Window { resizable: true destroyOnInvisible: true x: 40; y: 40 - implicitWidth: 384; implicitHeight: 640 + implicitWidth: 400; implicitHeight: 695 minSize: Qt.vector2d(200, 300) HifiConstants { id: hifi } @@ -87,8 +87,11 @@ Window { Column { width: pane.contentWidth - HifiControls.StaticSection { + HifiControls.ContentSection { name: "Currently Running" + isFirst: true + + HifiControls.VerticalSpacer {} Row { spacing: hifi.dimensions.contentSpacing.x @@ -106,6 +109,8 @@ Window { } } + HifiControls.VerticalSpacer {} + HifiControls.Table { tableModel: runningScriptsModel height: 185 @@ -113,11 +118,16 @@ Window { anchors.left: parent.left anchors.right: parent.right } + + HifiControls.VerticalSpacer { + height: 2 // Table view draws a little taller than it's height. + } } - HifiControls.StaticSection { + HifiControls.ContentSection { name: "Load Scripts" - hasSeparator: true + + HifiControls.VerticalSpacer {} Row { spacing: hifi.dimensions.contentSpacing.x @@ -161,18 +171,21 @@ Window { } } + HifiControls.VerticalSpacer {} + HifiControls.TextField { id: filterEdit anchors.left: parent.left anchors.right: parent.right focus: true colorScheme: hifi.colorSchemes.dark - //placeholderText: "filter" - label: "Filter" + placeholderText: "filter" onTextChanged: scriptsModel.filterRegExp = new RegExp("^.*" + text + ".*$", "i") Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i") } + HifiControls.VerticalSpacer {} + HifiControls.Tree { id: treeView height: 155 @@ -182,6 +195,8 @@ Window { anchors.right: parent.right } + HifiControls.VerticalSpacer {} + HifiControls.TextField { id: selectedScript anchors.left: parent.left diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 77e74163c3..108cf8a221 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -48,11 +48,13 @@ Item { // Other colors readonly property color white: "#ffffff" + readonly property color gray: "#808080" readonly property color black: "#000000" // Semitransparent readonly property color white50: "#80ffffff" readonly property color white30: "#4dffffff" readonly property color white25: "#40ffffff" + readonly property color transparent: "#00ffffff" // Control specific colors readonly property color tableRowLightOdd: white50 @@ -61,6 +63,26 @@ Item { readonly property color tableRowDarkEven: "#a6181818" readonly property color tableScrollHandle: "#707070" readonly property color tableScrollBackground: "#323232" + readonly property color checkboxLightStart: "#ffffff" + readonly property color checkboxLightFinish: "#afafaf" + readonly property color checkboxDarkStart: "#7d7d7d" + readonly property color checkboxDarkFinish: "#6b6a6b" + readonly property color checkboxChecked: primaryHighlight + readonly property color checkboxCheckedBorder: "#36cdff" + readonly property color sliderGutterLight: "#d4d4d4" + readonly property color sliderGutterDark: "#252525" + readonly property color sliderBorderLight: "#afafaf" + readonly property color sliderBorderDark: "#7d7d7d" + readonly property color sliderLightStart: "#ffffff" + readonly property color sliderLightFinish: "#afafaf" + readonly property color sliderDarkStart: "#7d7d7d" + readonly property color sliderDarkFinish: "#6b6a6b" + readonly property color dropDownPressedLight: "#d4d4d4" + readonly property color dropDownPressedDark: "#afafaf" + readonly property color dropDownLightStart: "#ffffff" + readonly property color dropDownLightFinish: "#afafaf" + readonly property color dropDownDarkStart: "#7d7d7d" + readonly property color dropDownDarkFinish: "#6b6a6b" } Item { @@ -76,39 +98,54 @@ Item { readonly property real borderWidth: largeScreen ? 2 : 1 readonly property vector2d contentMargin: Qt.vector2d(12, 24) readonly property vector2d contentSpacing: Qt.vector2d(8, 12) + readonly property real labelPadding: 40 readonly property real textPadding: 8 + readonly property real sliderHandleSize: 18 + readonly property real sliderGrooveHeight: 8 + readonly property real spinnerSize: 42 readonly property real tablePadding: 12 readonly property real tableRowHeight: largeScreen ? 26 : 23 readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) readonly property real modalDialogTitleHeight: 40 + readonly property real controlLineHeight: 29 // Height of spinbox control on 1920 x 1080 monitor + readonly property real controlInterlineHeight: 22 // 75% of controlLineHeight } Item { id: fontSizes // In pixels - readonly property real overlayTitle: dimensions.largeScreen? 18 : 14 - readonly property real tabName: dimensions.largeScreen? 12 : 10 - readonly property real sectionName: dimensions.largeScreen? 12 : 10 - readonly property real inputLabel: dimensions.largeScreen? 14 : 10 - readonly property real textFieldInput: dimensions.largeScreen? 15 : 12 - readonly property real tableText: dimensions.largeScreen? 15 : 12 - readonly property real buttonLabel: dimensions.largeScreen? 13 : 9 - readonly property real iconButton: dimensions.largeScreen? 13 : 9 - readonly property real listItem: dimensions.largeScreen? 15 : 11 - readonly property real tabularData: dimensions.largeScreen? 15 : 11 - readonly property real logs: dimensions.largeScreen? 16 : 12 - readonly property real code: dimensions.largeScreen? 16 : 12 - readonly property real rootMenu: dimensions.largeScreen? 15 : 11 - readonly property real menuItem: dimensions.largeScreen? 15 : 11 - readonly property real shortcutText: dimensions.largeScreen? 13 : 9 + readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14 + readonly property real tabName: dimensions.largeScreen ? 12 : 10 + readonly property real sectionName: dimensions.largeScreen ? 12 : 10 + readonly property real inputLabel: dimensions.largeScreen ? 14 : 10 + readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12 + readonly property real tableText: dimensions.largeScreen ? 15 : 12 + readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9 + readonly property real iconButton: dimensions.largeScreen ? 13 : 9 + readonly property real listItem: dimensions.largeScreen ? 15 : 11 + readonly property real tabularData: dimensions.largeScreen ? 15 : 11 + readonly property real logs: dimensions.largeScreen ? 16 : 12 + readonly property real code: dimensions.largeScreen ? 16 : 12 + readonly property real rootMenu: dimensions.largeScreen ? 15 : 11 + readonly property real menuItem: dimensions.largeScreen ? 15 : 11 + readonly property real shortcutText: dimensions.largeScreen ? 13 : 9 + readonly property real carat: dimensions.largeScreen ? 38 : 30 + readonly property real disclosureButton: dimensions.largeScreen ? 20 : 15 } Item { id: glyphs + readonly property string backward: "E" + readonly property string caratDn: "5" + readonly property string caratR: "3" + readonly property string caratUp: "6" readonly property string close: "w" readonly property string closeInverted: "x" readonly property string closeSmall: "C" + readonly property string disclosureButtonCollapse: "M" + readonly property string disclosureButtonExpand: "L" readonly property string disclosureCollapse: "Z" readonly property string disclosureExpand: "B" + readonly property string forward: "D" readonly property string pin: "y" readonly property string pinInverted: "z" readonly property string reloadSmall: "a" diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml index 4e4c2df8f2..04905656ce 100644 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ b/interface/resources/qml/windows-uit/DefaultFrame.qml @@ -99,8 +99,8 @@ Frame { DropShadow { source: titleText anchors.fill: titleText - horizontalOffset: 1 - verticalOffset: 1 + horizontalOffset: 2 + verticalOffset: 2 samples: 2 color: hifi.colors.baseGrayShadow60 visible: (window && window.focus) diff --git a/interface/resources/qml/windows-uit/Window.qml b/interface/resources/qml/windows-uit/Window.qml index ae73a53d65..429c6d5dcc 100644 --- a/interface/resources/qml/windows-uit/Window.qml +++ b/interface/resources/qml/windows-uit/Window.qml @@ -58,6 +58,8 @@ Fadable { // The content to place inside the window, determined by the client default property var content + property var footer: Item { } // Optional static footer at the bottom of the dialog. + function setDefaultFocus() {} // Default function; can be overridden by dialogs. property var rectifier: Timer { @@ -125,7 +127,8 @@ Fadable { // Scrollable window content. property var pane: Item { property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 11 : 0) + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height anchors.fill: parent anchors.rightMargin: isScrolling ? 11 : 0 @@ -162,6 +165,7 @@ Fadable { verticalScrollBarPolicy: Qt.ScrollBarAsNeeded anchors.fill: parent anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 style: ScrollViewStyle { @@ -203,7 +207,46 @@ Fadable { } } } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footer.height + 2 * hifi.dimensions.contentSpacing.y + color: hifi.colors.baseGray + visible: footer.height > 0 + + Item { + // Horizontal rule. + anchors.fill: parent + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors.fill: parent + anchors.topMargin: 3 // Horizontal rule. + children: [ footer ] + } + } } + children: [ swallower, frame, pane, activator ] Component.onCompleted: { raise(); setDefaultFocus(); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 21377fa945..81b633a1aa 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -370,7 +370,6 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -528,13 +527,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : audioThread->start(); - // Setup AssetClient - auto assetClient = DependencyManager::get(); - QThread* assetThread = new QThread; - assetThread->setObjectName("Asset Thread"); - assetClient->moveToThread(assetThread); - connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init); - assetThread->start(); + ResourceManager::init(); // Setup MessagesClient auto messagesClient = DependencyManager::get(); @@ -547,11 +540,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); - connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; @@ -588,6 +580,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // set the account manager's root URL and trigger a login request if we don't have the access token + accountManager.setIsAgent(true); accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); UserActivityLogger::getInstance().launch(applicationVersion()); @@ -644,13 +637,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket); identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); - QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkDiskCache* cache = new QNetworkDiskCache(); - cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); - cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); - networkAccessManager.setCache(cache); - ResourceCache::setRequestLimit(3); _glWidget = new GLCanvas(); @@ -661,15 +647,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); + #ifdef Q_OS_MAC - // OSX doesn't seem to provide for hiding the cursor only on the GL widget - _window->setCursor(Qt::BlankCursor); + auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget #else - // On windows and linux, hiding the top level cursor also means it's invisible - // when hovering over the window menu, which is a pain, so only hide it for - // the GL surface - _glWidget->setCursor(Qt::BlankCursor); + // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the + // window menu, which is a pain, so only hide it for the GL surface + auto cursorTarget = _glWidget; #endif + cursorTarget->setCursor(Qt::BlankCursor); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); @@ -903,9 +889,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : SpacemouseManager::getInstance().init(); #endif - auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); - // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it auto entityScriptingInterface = DependencyManager::get(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, @@ -981,6 +964,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _idleTimer->start(0); } + +void Application::checkChangeCursor() { + QMutexLocker locker(&_changeCursorLock); + if (_cursorNeedsChanging) { +#ifdef Q_OS_MAC + auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget +#else + // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the + // window menu, which is a pain, so only hide it for the GL surface + auto cursorTarget = _glWidget; +#endif + cursorTarget->setCursor(_desiredCursor); + + _cursorNeedsChanging = false; + } +} + +void Application::showCursor(const QCursor& cursor) { + QMutexLocker locker(&_changeCursorLock); + _desiredCursor = cursor; + _cursorNeedsChanging = true; +} + void Application::aboutToQuit() { emit beforeAboutToQuit(); @@ -1062,13 +1068,6 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); } -void Application::emptyLocalCache() { - if (auto cache = NetworkAccessManager::getInstance().cache()) { - qDebug() << "DiskCacheEditor::clear(): Clearing disk cache."; - cache->clear(); - } -} - Application::~Application() { EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(NULL); @@ -1106,11 +1105,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); - // cleanup the AssetClient thread - QThread* assetThread = DependencyManager::get()->thread(); - DependencyManager::destroy(); - assetThread->quit(); - assetThread->wait(); + ResourceManager::cleanup(); QThread* nodeThread = DependencyManager::get()->thread(); @@ -2431,6 +2426,9 @@ void Application::idle(uint64_t now) { return; // bail early, nothing to do here. } + + checkChangeCursor(); + Stats::getInstance()->updateStats(); AvatarInputs::getInstance()->update(); @@ -2946,9 +2944,9 @@ void Application::updateMyAvatarLookAtPosition() { } else { // I am not looking at anyone else, so just look forward if (isHMD) { - glm::mat4 headPose = _avatarUpdate->getHeadPose() ; + glm::mat4 headPose = _avatarUpdate->getHeadPose(); glm::quat headRotation = glm::quat_cast(headPose); - lookAtSpot = _myCamera.getPosition() + + lookAtSpot = myAvatar->getPosition() + myAvatar->getOrientation() * (headRotation * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + @@ -3055,7 +3053,7 @@ void Application::reloadResourceCaches() { _viewFrustum.setOrientation(glm::quat()); queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); - emptyLocalCache(); + DependencyManager::get()->clearCache(); DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); @@ -3818,7 +3816,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se // Setup the current Zone Entity lighting { auto stage = DependencyManager::get()->getSkyStage(); - DependencyManager::get()->setGlobalLight(stage->getSunLight(), stage->getSkybox()->getCubemap()); + DependencyManager::get()->setGlobalLight(stage->getSunLight()); } { @@ -3954,37 +3952,13 @@ void Application::clearDomainOctreeDetails() { void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); clearDomainOctreeDetails(); - _domainConnectionRefusals.clear(); // disable physics until we have enough information about our new location to not cause craziness. _physicsEnabled = false; } -void Application::handleDomainConnectionDeniedPacket(QSharedPointer message) { - // Read deny reason from packet - quint16 reasonSize; - message->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); - // output to the log so the user knows they got a denied connection request - // and check and signal for an access token so that we can make sure they are logged in - qCDebug(interfaceapp) << "The domain-server denied a connection request: " << reason; - qCDebug(interfaceapp) << "You may need to re-log to generate a keypair so you can provide a username signature."; - - if (!_domainConnectionRefusals.contains(reason)) { - _domainConnectionRefusals.append(reason); - emit domainConnectionRefused(reason); - } - - AccountManager::getInstance().checkAndSignalForAccessToken(); -} - -void Application::connectedToDomain(const QString& hostname) { - AccountManager& accountManager = AccountManager::getInstance(); - const QUuid& domainID = DependencyManager::get()->getDomainHandler().getUUID(); - - if (accountManager.isLoggedIn() && !domainID.isNull()) { - _notifiedPacketVersionMismatchThisDomain = false; - } +void Application::resettingDomain() { + _notifiedPacketVersionMismatchThisDomain = false; } void Application::nodeAdded(SharedNodePointer node) { @@ -4521,33 +4495,6 @@ void Application::openUrl(const QUrl& url) { } } -void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) { - // from the domain-handler, figure out the satoshi cost per voxel and per meter cubed - const QString VOXEL_SETTINGS_KEY = "voxels"; - const QString PER_VOXEL_COST_KEY = "per-voxel-credits"; - const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits"; - const QString VOXEL_WALLET_UUID = "voxel-wallet"; - - const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject(); - - qint64 satoshisPerVoxel = 0; - qint64 satoshisPerMeterCubed = 0; - QUuid voxelWalletUUID; - - if (!domainSettingsObject.isEmpty()) { - float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble(); - float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble(); - - satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT); - satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT); - - voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString()); - } - - qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed"; - qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID; -} - void Application::loadDialog() { auto scriptEngines = DependencyManager::get(); QString fileNameString = OffscreenUi::getOpenFileName( diff --git a/interface/src/Application.h b/interface/src/Application.h index d205ce8041..c16175a333 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -120,6 +120,8 @@ public: QSize getDeviceSize() const; bool hasFocus() const; + void showCursor(const QCursor& cursor); + bool isThrottleRendering() const; Camera* getCamera() { return &_myCamera; } @@ -225,7 +227,6 @@ signals: void svoImportRequested(const QString& url); void checkBackgroundDownloads(); - void domainConnectionRefused(const QString& reason); void fullAvatarURLChanged(const QString& newValue, const QString& modelName); @@ -288,16 +289,13 @@ private slots: void idle(uint64_t now); void aboutToQuit(); - void connectedToDomain(const QString& hostname); + void resettingDomain(); void audioMuteToggled(); void faceTrackerMuteToggled(); void activeChanged(Qt::ApplicationState state); - void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void handleDomainConnectionDeniedPacket(QSharedPointer message); - void notifyPacketVersionMismatch(); void loadSettings(); @@ -328,8 +326,6 @@ private: void cleanupBeforeQuit(); - void emptyLocalCache(); - void update(float deltaTime); void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue); @@ -476,7 +472,6 @@ private: typedef bool (Application::* AcceptURLMethod)(const QString &); static const QHash _acceptedExtensions; - QList _domainConnectionRefusals; glm::uvec2 _renderResolution; int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS; @@ -515,6 +510,11 @@ private: QTimer* _idleTimer { nullptr }; bool _fakedMouseEvent { false }; + + void checkChangeCursor(); + mutable QMutex _changeCursorLock { QMutex::Recursive }; + QCursor _desiredCursor{ Qt::BlankCursor }; + bool _cursorNeedsChanging { false }; }; #endif // hifi_Application_h diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index ce5facb580..f9cd7679ae 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -22,11 +22,8 @@ #include #include -#include "DataServerAccountInfo.h" #include "Menu.h" -Q_DECLARE_METATYPE(DataServerAccountInfo) - static const QString RUNNING_MARKER_FILENAME = "Interface.running"; void CrashHandler::checkForAndHandleCrash() { @@ -57,7 +54,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { layout->addWidget(label); QRadioButton* option1 = new QRadioButton("Reset all my settings"); - QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info."); + QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info."); QRadioButton* option3 = new QRadioButton("Continue with my current settings"); option3->setChecked(true); layout->addWidget(option1); @@ -79,7 +76,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { return CrashHandler::DELETE_INTERFACE_INI; } if (option2->isChecked()) { - return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO; + return CrashHandler::RETAIN_AVATAR_INFO; } } @@ -88,7 +85,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { } void CrashHandler::handleCrash(CrashHandler::Action action) { - if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) { // CrashHandler::DO_NOTHING or unexpected value return; } @@ -101,18 +98,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { const QString DISPLAY_NAME_KEY = "displayName"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; - const QString ACCOUNTS_GROUP = "accounts"; QString displayName; QUrl fullAvatarURL; QString fullAvatarModelName; QUrl address; - QMap accounts; - if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { - // Read login and avatar info - - qRegisterMetaType("DataServerAccountInfo"); - qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); + if (action == CrashHandler::RETAIN_AVATAR_INFO) { + // Read avatar info // Location and orientation settings.beginGroup(ADDRESS_MANAGER_GROUP); @@ -125,13 +117,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); settings.endGroup(); - - // Accounts - settings.beginGroup(ACCOUNTS_GROUP); - foreach(const QString& key, settings.allKeys()) { - accounts.insert(key, settings.value(key).value()); - } - settings.endGroup(); } // Delete Interface.ini @@ -140,8 +125,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settingsFile.remove(); } - if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { - // Write login and avatar info + if (action == CrashHandler::RETAIN_AVATAR_INFO) { + // Write avatar info // Location and orientation settings.beginGroup(ADDRESS_MANAGER_GROUP); @@ -154,13 +139,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); settings.endGroup(); - - // Accounts - settings.beginGroup(ACCOUNTS_GROUP); - foreach(const QString& key, accounts.keys()) { - settings.setValue(key, QVariant::fromValue(accounts.value(key))); - } - settings.endGroup(); } } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index fc754cf1ac..61361b6107 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -25,7 +25,7 @@ public: private: enum Action { DELETE_INTERFACE_INI, - RETAIN_LOGIN_AND_AVATAR_INFO, + RETAIN_AVATAR_INFO, DO_NOTHING }; diff --git a/interface/src/MainWindow.cpp b/interface/src/MainWindow.cpp index 34e1638e94..16aedc4bb7 100644 --- a/interface/src/MainWindow.cpp +++ b/interface/src/MainWindow.cpp @@ -31,7 +31,6 @@ MainWindow::MainWindow(QWidget* parent) : _windowState("WindowState", 0) { setAcceptDrops(true); - _trayIcon.show(); } void MainWindow::restoreGeometry() { diff --git a/interface/src/MainWindow.h b/interface/src/MainWindow.h index 6a401cf2b7..eb262e0f97 100644 --- a/interface/src/MainWindow.h +++ b/interface/src/MainWindow.h @@ -13,7 +13,6 @@ #define __hifi__MainWindow__ #include -#include #include @@ -43,7 +42,6 @@ protected: private: Setting::Handle _windowGeometry; Setting::Handle _windowState; - QSystemTrayIcon _trayIcon; }; #endif /* defined(__hifi__MainWindow__) */ diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 64ab76c64c..fa87a5f657 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -342,9 +342,9 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename void ModelPackager::listTextures() { _textures.clear(); foreach (const FBXMaterial mat, _geometry->materials) { - if (!mat.diffuseTexture.filename.isEmpty() && mat.diffuseTexture.content.isEmpty() && - !_textures.contains(mat.diffuseTexture.filename)) { - _textures << mat.diffuseTexture.filename; + if (!mat.albedoTexture.filename.isEmpty() && mat.albedoTexture.content.isEmpty() && + !_textures.contains(mat.albedoTexture.filename)) { + _textures << mat.albedoTexture.filename; } if (!mat.normalTexture.filename.isEmpty() && mat.normalTexture.content.isEmpty() && !_textures.contains(mat.normalTexture.filename)) { diff --git a/interface/src/ModelSelector.cpp b/interface/src/ModelSelector.cpp index c4922bdd70..2f85849fbe 100644 --- a/interface/src/ModelSelector.cpp +++ b/interface/src/ModelSelector.cpp @@ -18,8 +18,6 @@ #include "ModelSelector.h" -static const QString AVATAR_HEAD_STRING = "Avatar Head Only"; -static const QString AVATAR_BODY_STRING = "Avatar Body Only"; static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head"; static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment"; static const QString ENTITY_MODEL_STRING = "Entity Model"; @@ -35,8 +33,7 @@ ModelSelector::ModelSelector() { form->addRow("Model File:", _browseButton); _modelType = new QComboBox(this); - _modelType->addItem(AVATAR_HEAD_STRING); - _modelType->addItem(AVATAR_BODY_STRING); + _modelType->addItem(AVATAR_HEAD_AND_BODY_STRING); _modelType->addItem(AVATAR_ATTACHEMENT_STRING); _modelType->addItem(ENTITY_MODEL_STRING); @@ -55,11 +52,7 @@ QFileInfo ModelSelector::getFileInfo() const { FSTReader::ModelType ModelSelector::getModelType() const { QString text = _modelType->currentText(); - if (text == AVATAR_HEAD_STRING) { - return FSTReader::HEAD_MODEL; - } else if (text == AVATAR_BODY_STRING) { - return FSTReader::BODY_ONLY_MODEL; - } else if (text == AVATAR_HEAD_AND_BODY_STRING) { +if (text == AVATAR_HEAD_AND_BODY_STRING) { return FSTReader::HEAD_AND_BODY_MODEL; } else if (text == AVATAR_ATTACHEMENT_STRING) { return FSTReader::ATTACHMENT_MODEL; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 83351d5188..0bd134bef5 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -400,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { frustum = qApp->getDisplayViewFrustum(); } - if (frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) { + if (!frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) { endRender(); return; } diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5b493c4215..b62cae1d58 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -153,25 +153,6 @@ std::shared_ptr AvatarActionHold::getTarget(float deltaTimeStep, glm::qu palmPosition = holdingAvatar->getLeftPalmPosition(); palmRotation = holdingAvatar->getLeftPalmRotation(); } - - // In this case we are simulating the grab of another avatar. - // Because the hand controller velocity for their palms is not transmitted over the - // network, we have to synthesize our own. - - if (_previousSet) { - // smooth linear velocity over two frames - glm::vec3 positionalDelta = palmPosition - _previousPositionalTarget; - linearVelocity = (positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep); - glm::quat deltaRotation = palmRotation * glm::inverse(_previousRotationalTarget); - float rotationAngle = glm::angle(deltaRotation); - if (rotationAngle > EPSILON) { - angularVelocity = glm::normalize(glm::axis(deltaRotation)); - angularVelocity *= (rotationAngle / deltaTimeStep); - } - - _previousPositionalDelta = positionalDelta; - _previousDeltaTimeStep = deltaTimeStep; - } } rotation = palmRotation * _relativeRotation; @@ -278,6 +259,7 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { }); forceBodyNonStatic(); + activateBody(true); } bool AvatarActionHold::updateArguments(QVariantMap arguments) { diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 68a13ba227..4881e3eaec 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -31,7 +31,14 @@ void AvatarUpdate::synchronousProcess() { // Keep our own updated value, so that our asynchronous code can consult it. _isHMDMode = qApp->isHMDMode(); auto frameCount = qApp->getFrameCount(); - _headPose = qApp->getActiveDisplayPlugin()->getHeadPose(frameCount); + + QSharedPointer manager = DependencyManager::get(); + MyAvatar* myAvatar = manager->getMyAvatar(); + assert(myAvatar); + + // transform the head pose from the displayPlugin into avatar coordinates. + glm::mat4 invAvatarMat = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition())); + _headPose = invAvatarMat * (myAvatar->getSensorToWorldMatrix() * qApp->getActiveDisplayPlugin()->getHeadPose(frameCount)); if (!isThreaded()) { process(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fc715eebe9..397627434f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -849,7 +850,7 @@ void MyAvatar::updateLookAtTargetAvatar() { avatar->setIsLookAtTarget(false); if (!avatar->isMyAvatar() && avatar->isInitialized() && (distanceTo < GREATEST_LOOKING_AT_DISTANCE * getUniformScale())) { - float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - cameraPosition)); + float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition())); if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) { _lookAtTargetAvatar = avatarPointer; _targetAvatarPosition = avatarPointer->getPosition(); @@ -864,36 +865,29 @@ void MyAvatar::updateLookAtTargetAvatar() { // Let's get everything to world space: glm::vec3 avatarLeftEye = getHead()->getLeftEyePosition(); glm::vec3 avatarRightEye = getHead()->getRightEyePosition(); - // When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok. - // By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.) - // This might be more work than needed for any given use, but as we explore different formulations, we go mad if we don't work in world space. - glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left); - glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right); - glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); - glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); - auto humanSystem = qApp->getViewFrustum(); - glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal); - glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal); // First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point. // (We will be adding that offset to the camera position, after making some other adjustments.) glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition(); - // Scale by proportional differences between avatar and human. - float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye); - float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); - gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; + // scale gazeOffset by IPD, if wearing an HMD. + if (qApp->isHMDMode()) { + glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left); + glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right); + glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); + glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); + auto humanSystem = qApp->getViewFrustum(); + glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal); + glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal); - // If the camera is also not oriented with the head, adjust by getting the offset in head-space... - /* Not needed (i.e., code is a no-op), but I'm leaving the example code here in case something like this is needed someday. - glm::quat avatarHeadOrientation = getHead()->getOrientation(); - glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset; - // ... and treat that as though it were in camera space, bringing it back to world space. - // But camera is fudged to make the picture feel like the avatar's orientation. - glm::quat humanOrientation = humanSystem->getOrientation(); // or just avatar getOrienation() ? - gazeOffset = humanOrientation * gazeOffsetLocalToHead; - glm::vec3 corrected = humanSystem->getPosition() + gazeOffset; - */ + auto hmdInterface = DependencyManager::get(); + float ipdScale = hmdInterface->getIPDScale(); + + // Scale by proportional differences between avatar and human. + float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale; + float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); + gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; + } // And now we can finally add that offset to the camera. glm::vec3 corrected = qApp->getViewFrustum()->getPosition() + gazeOffset; @@ -1097,7 +1091,8 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setTargetVelocity(getTargetVelocity()); _characterController.setPositionAndOrientation(getPosition(), getOrientation()); if (qApp->isHMDMode()) { - _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix); + bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; + _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput); } else { _follow.deactivate(); } @@ -1339,11 +1334,11 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { _prevShouldDrawHead = shouldDrawHead; } -const float RENDER_HEAD_CUTOFF_DISTANCE = 0.6f; +const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f; bool MyAvatar::cameraInsideHead() const { const glm::vec3 cameraPosition = qApp->getCamera()->getPosition(); - return glm::length(cameraPosition - getDefaultEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale()); + return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale()); } bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { @@ -1776,25 +1771,6 @@ glm::quat MyAvatar::getWorldBodyOrientation() const { return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix); } -#if 0 -// derive avatar body position and orientation from the current HMD Sensor location. -// results are in sensor space -glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { - if (_rig) { - // orientation - const glm::quat hmdOrientation = getHMDSensorOrientation(); - const glm::quat yaw = cancelOutRollAndPitch(hmdOrientation); - // position - // we flip about yAxis when going from "root" to "avatar" frame - // and we must also apply "yaw" to get into HMD frame - glm::quat rotY180 = glm::angleAxis((float)M_PI, glm::vec3(0.0f, 1.0f, 0.0f)); - glm::vec3 eyesInAvatarFrame = rotY180 * yaw * _rig->getEyesInRootFrame(); - glm::vec3 bodyPos = getHMDSensorPosition() - eyesInAvatarFrame; - return createMatFromQuatAndPos(yaw, bodyPos); - } - return glm::mat4(); -} -#else // old school meat hook style glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { @@ -1835,7 +1811,6 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos); } -#endif glm::vec3 MyAvatar::getPositionForAudio() { switch (_audioListenerMode) { @@ -1884,58 +1859,132 @@ void MyAvatar::lateUpdatePalms() { static const float FOLLOW_TIME = 0.5f; -void MyAvatar::FollowHelper::deactivate() { - _timeRemaining = 0.0f; +MyAvatar::FollowHelper::FollowHelper() { + deactivate(); } -void MyAvatar::FollowHelper::activate() { +void MyAvatar::FollowHelper::deactivate() { + for (int i = 0; i < NumFollowTypes; i++) { + deactivate((FollowType)i); + } +} + +void MyAvatar::FollowHelper::deactivate(FollowType type) { + assert(type >= 0 && type < NumFollowTypes); + _timeRemaining[(int)type] = 0.0f; +} + +void MyAvatar::FollowHelper::activate(FollowType type) { + assert(type >= 0 && type < NumFollowTypes); // TODO: Perhaps, the follow time should be proportional to the displacement. - _timeRemaining = FOLLOW_TIME; + _timeRemaining[(int)type] = FOLLOW_TIME; +} + +bool MyAvatar::FollowHelper::isActive(FollowType type) const { + assert(type >= 0 && type < NumFollowTypes); + return _timeRemaining[(int)type] > 0.0f; } bool MyAvatar::FollowHelper::isActive() const { - return _timeRemaining > 0.0f; + for (int i = 0; i < NumFollowTypes; i++) { + if (isActive((FollowType)i)) { + return true; + } + } + return false; } -bool MyAvatar::FollowHelper::shouldActivate(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +float MyAvatar::FollowHelper::getMaxTimeRemaining() const { + float max = 0.0f; + for (int i = 0; i < NumFollowTypes; i++) { + if (_timeRemaining[i] > max) { + max = _timeRemaining[i]; + } + } + return max; +} - const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 4.0f); +void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { + for (int i = 0; i < NumFollowTypes; i++) { + _timeRemaining[i] -= dt; + } +} +bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { + const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); - if (glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD) { + return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; +} + +bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { + + // -z axis of currentBodyMatrix in world space. + glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2])); + // x axis of currentBodyMatrix in world space. + glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0])); + glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + + float forwardLeanAmount = glm::dot(forward, offset); + float lateralLeanAmount = glm::dot(right, offset); + + const float MAX_LATERAL_LEAN = 0.3f; + const float MAX_FORWARD_LEAN = 0.15f; + const float MAX_BACKWARD_LEAN = 0.1f; + + if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { + return true; + } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { return true; } - const float CYLINDER_TOP = 0.1f; - const float CYLINDER_BOTTOM = -0.5f; - const float CYLINDER_RADIUS = 0.15f; - - glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - glm::vec3 radialOffset(offset.x, 0.0f, offset.z); - float radialDistance = glm::length(radialOffset); - - return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM) || (radialDistance > CYLINDER_RADIUS); + return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) { +bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { + + const float CYLINDER_TOP = 0.1f; + const float CYLINDER_BOTTOM = -1.5f; + + glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); +} + +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { _desiredBodyMatrix = desiredBodyMatrix; - if (!isActive() && shouldActivate(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { - activate(); + if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { + activate(Rotation); + } + if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { + activate(Horizontal); + } + if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(Vertical); } - if (isActive()) { - glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; - myAvatar.getCharacterController()->setFollowParameters(desiredWorldMatrix, _timeRemaining); - } else { - glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; - myAvatar.getCharacterController()->setFollowParameters(currentWorldMatrix, 0.0f); + glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; + glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; + + AnimPose followWorldPose(currentWorldMatrix); + if (isActive(Rotation)) { + followWorldPose.rot = glmExtractRotation(desiredWorldMatrix); } + if (isActive(Horizontal)) { + glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); + followWorldPose.trans.x = desiredTranslation.x; + followWorldPose.trans.z = desiredTranslation.z; + } + if (isActive(Vertical)) { + glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); + followWorldPose.trans.y = desiredTranslation.y; + } + + myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining()); } glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { if (isActive()) { float dt = myAvatar.getCharacterController()->getFollowTime(); - _timeRemaining -= dt; + decrementTimeRemaining(dt); // apply follow displacement to the body matrix. glm::vec3 worldLinearDisplacement = myAvatar.getCharacterController()->getFollowLinearDisplacement(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 637baa3c22..731cba8153 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -392,14 +392,29 @@ private: glm::mat4 _sensorToWorldMatrix; struct FollowHelper { + FollowHelper(); + + enum FollowType { + Rotation = 0, + Horizontal, + Vertical, + NumFollowTypes + }; glm::mat4 _desiredBodyMatrix; - float _timeRemaining { 0.0f }; + float _timeRemaining[NumFollowTypes]; void deactivate(); + void deactivate(FollowType type); void activate(); + void activate(FollowType type); bool isActive() const; - bool shouldActivate(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix); + bool isActive(FollowType followType) const; + float getMaxTimeRemaining() const; + void decrementTimeRemaining(float dt); + bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); }; FollowHelper _follow; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index b8c82498a1..4c9c0df1cb 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -25,7 +25,7 @@ WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); - connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) { static const QMetaMethod svoImportRequestedSignal = diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 99f0b4fdc4..efd69c7859 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -212,19 +212,21 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); //draw the mouse pointer - // Get the mouse coordinates and convert to NDC [-1, 1] - vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas... - vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); - // Invert the Y axis - mousePosition.y *= -1.0f; + if (getReticleVisible()) { + // Get the mouse coordinates and convert to NDC [-1, 1] + vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas... + vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); + // Invert the Y axis + mousePosition.y *= -1.0f; - Transform model; - model.setTranslation(vec3(mousePosition, 0)); - vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; - model.setScale(vec3(mouseSize, 1.0f)); - batch.setModelTransform(model); - bindCursorTexture(batch); - geometryCache->renderUnitQuad(batch, vec4(1)); + Transform model; + model.setTranslation(vec3(mousePosition, 0)); + vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; + model.setScale(vec3(mouseSize, 1.0f)); + batch.setModelTransform(model); + bindCursorTexture(batch); + geometryCache->renderUnitQuad(batch, vec4(1)); + } }); } @@ -301,7 +303,7 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int // look at borrowed from overlays float elevation = -asinf(relativePosition.y / glm::length(relativePosition)); float azimuth = atan2f(relativePosition.x, relativePosition.z); - glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, 0, -1)); // this extra *quat(vec3(0,0,-1)) was required to get the quad to flip this seems like we could optimize + glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, -PI, 0)); // this extra *quat(vec3(0,-PI,0)) was required to get the quad to flip this seems like we could optimize Transform transform; transform.setTranslation(relativePosition); @@ -335,9 +337,21 @@ QPointF ApplicationCompositor::getMouseEventPosition(QMouseEvent* event) { bool ApplicationCompositor::shouldCaptureMouse() const { // if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu - return qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); + return _allowMouseCapture && qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); } +void ApplicationCompositor::setAllowMouseCapture(bool capture) { + if (qApp->isHMDMode()) { + if (capture) { + qApp->showCursor(Qt::BlankCursor); + } else { + qApp->showCursor(Qt::ArrowCursor); + } + } + _allowMouseCapture = capture; +} + + void ApplicationCompositor::handleLeaveEvent() { if (shouldCaptureMouse()) { diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h index 32835ace5a..324250deb1 100644 --- a/interface/src/ui/ApplicationCompositor.h +++ b/interface/src/ui/ApplicationCompositor.h @@ -106,6 +106,9 @@ public: bool shouldCaptureMouse() const; + bool getAllowMouseCapture() const { return _allowMouseCapture; } + void setAllowMouseCapture(bool capture); + /// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false bool getReticleOverDesktop() const; void setReticleOverDesktop(bool value) { _isOverDesktop = value; } @@ -162,6 +165,8 @@ private: bool _reticleOverQml { false }; + bool _allowMouseCapture { true }; + ReticleInterface* _reticleInterface; }; @@ -173,12 +178,17 @@ class ReticleInterface : public QObject { Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) + Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay) public: ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {} Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); } + + Q_INVOKABLE bool getAllowMouseCapture() { return _compositor->getAllowMouseCapture(); } + Q_INVOKABLE void setAllowMouseCapture(bool value) { return _compositor->setAllowMouseCapture(value); } + Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); } Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); } diff --git a/interface/src/ui/DiskCacheEditor.cpp b/interface/src/ui/DiskCacheEditor.cpp index d3a40e6439..ed85b87d63 100644 --- a/interface/src/ui/DiskCacheEditor.cpp +++ b/interface/src/ui/DiskCacheEditor.cpp @@ -9,23 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "DiskCacheEditor.h" #include #include #include #include #include -#include +#include #include -#include +#include -#include "DiskCacheEditor.h" #include "OffscreenUi.h" DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) { - } QWindow* DiskCacheEditor::windowHandle() { @@ -33,7 +31,6 @@ QWindow* DiskCacheEditor::windowHandle() { } void DiskCacheEditor::toggle() { - qDebug() << "DiskCacheEditor::toggle()"; if (!_dialog) { makeDialog(); } @@ -88,17 +85,17 @@ void DiskCacheEditor::makeDialog() { Q_CHECK_PTR(_maxSize); _maxSize->setAlignment(Qt::AlignLeft); layout->addWidget(_maxSize, 2, 1, 1, 3); - + refresh(); - - - QPushButton* refreshCacheButton = new QPushButton(_dialog); - Q_CHECK_PTR(refreshCacheButton); - refreshCacheButton->setText("Refresh"); - refreshCacheButton->setToolTip("Reload the cache stats."); - connect(refreshCacheButton, SIGNAL(clicked()), SLOT(refresh())); - layout->addWidget(refreshCacheButton, 3, 2); - + + + static const int REFRESH_INTERVAL = 100; // msec + _refreshTimer = new QTimer(_dialog); + _refreshTimer->setInterval(REFRESH_INTERVAL); + _refreshTimer->setSingleShot(false); + QObject::connect(_refreshTimer.data(), &QTimer::timeout, this, &DiskCacheEditor::refresh); + _refreshTimer->start(); + QPushButton* clearCacheButton = new QPushButton(_dialog); Q_CHECK_PTR(clearCacheButton); clearCacheButton->setText("Clear"); @@ -108,7 +105,11 @@ void DiskCacheEditor::makeDialog() { } void DiskCacheEditor::refresh() { - static const std::function stringify = [](qint64 number) { + DependencyManager::get()->cacheInfoRequest(this, "cacheInfoCallback"); +} + +void DiskCacheEditor::cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize) { + static const auto stringify = [](qint64 number) { static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB"; static const qint64 CHUNK = 1024; QString unit; @@ -122,30 +123,24 @@ void DiskCacheEditor::refresh() { } return QString("%0 %1").arg(number).arg(UNITS[i]); }; - QNetworkDiskCache* cache = qobject_cast(NetworkAccessManager::getInstance().cache()); if (_path) { - _path->setText(cache->cacheDirectory()); + _path->setText(cacheDirectory); } if (_size) { - _size->setText(stringify(cache->cacheSize())); + _size->setText(stringify(cacheSize)); } if (_maxSize) { - _maxSize->setText(stringify(cache->maximumCacheSize())); + _maxSize->setText(stringify(maximumCacheSize)); } } void DiskCacheEditor::clear() { - QMessageBox::StandardButton buttonClicked = - OffscreenUi::question(_dialog, "Clearing disk cache", - "You are about to erase all the content of the disk cache, " - "are you sure you want to do that?", - QMessageBox::Ok | QMessageBox::Cancel); + auto buttonClicked = OffscreenUi::question(_dialog, "Clearing disk cache", + "You are about to erase all the content of the disk cache, " + "are you sure you want to do that?", + QMessageBox::Ok | QMessageBox::Cancel); if (buttonClicked == QMessageBox::Ok) { - if (auto cache = NetworkAccessManager::getInstance().cache()) { - qDebug() << "DiskCacheEditor::clear(): Clearing disk cache."; - cache->clear(); - } + DependencyManager::get()->clearCache(); } - refresh(); } diff --git a/interface/src/ui/DiskCacheEditor.h b/interface/src/ui/DiskCacheEditor.h index 5d673c4285..3f8fa1a883 100644 --- a/interface/src/ui/DiskCacheEditor.h +++ b/interface/src/ui/DiskCacheEditor.h @@ -18,6 +18,7 @@ class QDialog; class QLabel; class QWindow; +class QTimer; class DiskCacheEditor : public QObject { Q_OBJECT @@ -32,8 +33,9 @@ public slots: private slots: void refresh(); + void cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize); void clear(); - + private: void makeDialog(); @@ -41,6 +43,7 @@ private: QPointer _path; QPointer _size; QPointer _maxSize; + QPointer _refreshTimer; }; #endif // hifi_DiskCacheEditor_h \ No newline at end of file diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 78c2405ade..9f416f3117 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -36,8 +36,7 @@ void setupPreferences() { { auto getter = [=]()->QString {return myAvatar->getDisplayName(); }; auto setter = [=](const QString& value) { myAvatar->setDisplayName(value); }; - const QString label = "Avatar display name (optional)"; - auto preference = new EditPreference(AVATAR_BASICS, label, getter, setter); + auto preference = new EditPreference(AVATAR_BASICS, "Avatar display name (optional)", getter, setter); preference->setPlaceholderText("Not showing a name"); preferences->addPreference(preference); } @@ -45,8 +44,7 @@ void setupPreferences() { { auto getter = [=]()->QString {return myAvatar->getCollisionSoundURL(); }; auto setter = [=](const QString& value) { myAvatar->setCollisionSoundURL(value); }; - const QString label = "Avatar collision sound URL (optional)"; - auto preference = new EditPreference(AVATAR_BASICS, label, getter, setter); + auto preference = new EditPreference(AVATAR_BASICS, "Avatar collision sound URL (optional)", getter, setter); preference->setPlaceholderText("Enter the URL of a sound to play when you bump into something"); preferences->addPreference(preference); } @@ -54,18 +52,18 @@ void setupPreferences() { { auto getter = [=]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; auto setter = [=](const QString& value) { myAvatar->useFullAvatarURL(value, ""); }; - auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance: ", getter, setter); + auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance", getter, setter); preferences->addPreference(preference); } { auto getter = [=]()->bool {return myAvatar->getSnapTurn(); }; auto setter = [=](bool value) { myAvatar->setSnapTurn(value); }; - preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap Turn when in HMD", getter, setter)); + preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; - auto preference = new BrowsePreference("Snapshots", "Place my Snapshots here:", getter, setter); + auto preference = new BrowsePreference("Snapshots", "Put my snapshots here", getter, setter); preferences->addPreference(preference); } @@ -73,7 +71,7 @@ void setupPreferences() { { auto getter = []()->QString { return DependencyManager::get()->getScriptsLocation(); }; auto setter = [](const QString& value) { DependencyManager::get()->setScriptsLocation(value); }; - preferences->addPreference(new BrowsePreference("Scripts", "Load scripts from this directory:", getter, setter)); + preferences->addPreference(new BrowsePreference("Scripts", "Load scripts from this directory", getter, setter)); } preferences->addPreference(new ButtonPreference("Scripts", "Load Default Scripts", [] { @@ -83,14 +81,14 @@ void setupPreferences() { { auto getter = []()->bool {return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; - preferences->addPreference(new CheckPreference("Privacy", "Send Data", getter, setter)); + preferences->addPreference(new CheckPreference("Privacy", "Send data", getter, setter)); } static const QString LOD_TUNING("Level of Detail Tuning"); { auto getter = []()->float { return DependencyManager::get()->getDesktopLODDecreaseFPS(); }; auto setter = [](float value) { DependencyManager::get()->setDesktopLODDecreaseFPS(value); }; - auto preference = new SpinnerPreference(LOD_TUNING, "Minimum Desktop FPS", getter, setter); + auto preference = new SpinnerPreference(LOD_TUNING, "Minimum desktop FPS", getter, setter); preference->setMin(0); preference->setMax(120); preference->setStep(1); @@ -138,7 +136,7 @@ void setupPreferences() { { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScaleVerbose(value); }; // The hell? - auto preference = new SpinnerPreference(AVATAR_TUNING, "Avatar scale (default is 1.0)", getter, setter); + auto preference = new SpinnerPreference(AVATAR_TUNING, "Avatar scale (default is 1.0)", getter, setter); preference->setMin(0.01f); preference->setMax(99.9f); preference->setDecimals(2); @@ -170,7 +168,7 @@ void setupPreferences() { { auto getter = [=]()->QString { return myAvatar->getAnimGraphUrl().toString(); }; auto setter = [=](const QString& value) { myAvatar->setAnimGraphUrl(value); }; - auto preference = new EditPreference(AVATAR_TUNING, "Avatar Animation JSON", getter, setter); + auto preference = new EditPreference(AVATAR_TUNING, "Avatar animation JSON", getter, setter); preference->setPlaceholderText("default"); preferences->addPreference(preference); } @@ -179,7 +177,7 @@ void setupPreferences() { { auto getter = [=]()->float { return myAvatar->getPitchSpeed(); }; auto setter = [=](float value) { myAvatar->setPitchSpeed(value); }; - auto preference = new SpinnerPreference(AVATAR_CAMERA, "Camera Pitch Speed (degrees/second)", getter, setter); + auto preference = new SpinnerPreference(AVATAR_CAMERA, "Camera pitch speed (degrees/second)", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); preferences->addPreference(preference); @@ -187,7 +185,7 @@ void setupPreferences() { { auto getter = [=]()->float { return myAvatar->getYawSpeed(); }; auto setter = [=](float value) { myAvatar->setYawSpeed(value); }; - auto preference = new SpinnerPreference(AVATAR_CAMERA, "Camera Yaw Speed (degrees/second)", getter, setter); + auto preference = new SpinnerPreference(AVATAR_CAMERA, "Camera yaw speed (degrees/second)", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); preferences->addPreference(preference); @@ -197,13 +195,13 @@ void setupPreferences() { { auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getDynamicJitterBuffers(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setDynamicJitterBuffers(value); }; - preferences->addPreference(new CheckPreference(AUDIO, "Enable Dynamic Jitter Buffers", getter, setter)); + preferences->addPreference(new CheckPreference(AUDIO, "Enable dynamic jitter buffers", getter, setter)); } { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getDesiredJitterBufferFrames(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setStaticDesiredJitterBufferFrames(value); }; - auto preference = new SpinnerPreference(AUDIO, "Static Jitter Buffer Frames", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Static jitter buffer frames", getter, setter); preference->setMin(0); preference->setMax(10000); preference->setStep(1); @@ -212,7 +210,7 @@ void setupPreferences() { { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getMaxFramesOverDesired(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setMaxFramesOverDesired(value); }; - auto preference = new SpinnerPreference(AUDIO, "Max Frames Over Desired", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Max frames over desired", getter, setter); preference->setMax(10000); preference->setStep(1); preferences->addPreference(preference); @@ -220,12 +218,12 @@ void setupPreferences() { { auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getUseStDevForJitterCalc(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setUseStDevForJitterCalc(value); }; - preferences->addPreference(new CheckPreference(AUDIO, "Use Stddev for Dynamic Jitter Calc", getter, setter)); + preferences->addPreference(new CheckPreference(AUDIO, "Use standard deviation for dynamic jitter calc", getter, setter)); } { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getWindowStarveThreshold(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setWindowStarveThreshold(value); }; - auto preference = new SpinnerPreference(AUDIO, "Window A Starve Threshold", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Window A starve threshold", getter, setter); preference->setMax(10000); preference->setStep(1); preferences->addPreference(preference); @@ -233,7 +231,7 @@ void setupPreferences() { { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getWindowSecondsForDesiredCalcOnTooManyStarves(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setWindowSecondsForDesiredCalcOnTooManyStarves(value); }; - auto preference = new SpinnerPreference(AUDIO, "Window A (raise desired on N starves) Seconds)", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Window A (raise desired on N starves) seconds", getter, setter); preference->setMax(10000); preference->setStep(1); preferences->addPreference(preference); @@ -241,7 +239,7 @@ void setupPreferences() { { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getWindowSecondsForDesiredReduction(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setWindowSecondsForDesiredReduction(value); }; - auto preference = new SpinnerPreference(AUDIO, "Window B (desired ceiling) Seconds", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Window B (desired ceiling) seconds", getter, setter); preference->setMax(10000); preference->setStep(1); preferences->addPreference(preference); @@ -249,12 +247,12 @@ void setupPreferences() { { auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getRepetitionWithFade(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setRepetitionWithFade(value); }; - preferences->addPreference(new CheckPreference(AUDIO, "Repetition with Fade", getter, setter)); + preferences->addPreference(new CheckPreference(AUDIO, "Repetition with fade", getter, setter)); } { auto getter = []()->float { return DependencyManager::get()->getOutputBufferSize(); }; auto setter = [](float value) { DependencyManager::get()->setOutputBufferSize(value); }; - auto preference = new SpinnerPreference(AUDIO, "Output Buffer Initial Size (frames)", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Output buffer initial size (frames)", getter, setter); preference->setMin(1); preference->setMax(20); preference->setStep(1); @@ -263,13 +261,13 @@ void setupPreferences() { { auto getter = []()->bool {return DependencyManager::get()->getOutputStarveDetectionEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->setOutputStarveDetectionEnabled(value); }; - auto preference = new CheckPreference(AUDIO, "Output Starve Detection (Automatic Buffer Size Increase)", getter, setter); + auto preference = new CheckPreference(AUDIO, "Output starve detection (automatic buffer size increase)", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getOutputStarveDetectionThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setOutputStarveDetectionThreshold(value); }; - auto preference = new SpinnerPreference(AUDIO, "Output Starve Detection Threshold", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Output starve detection threshold", getter, setter); preference->setMin(1); preference->setMax(500); preference->setStep(1); @@ -278,7 +276,7 @@ void setupPreferences() { { auto getter = []()->float { return DependencyManager::get()->getOutputStarveDetectionPeriod(); }; auto setter = [](float value) { DependencyManager::get()->setOutputStarveDetectionPeriod(value); }; - auto preference = new SpinnerPreference(AUDIO, "Output Starve Detection Period (ms)", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Output starve detection period (ms)", getter, setter); preference->setMin(1); preference->setMax((float)999999999); preference->setStep(1); @@ -299,7 +297,7 @@ void setupPreferences() { { auto getter = []()->float { return qApp->getApplicationCompositor().getHmdUIAngularSize(); }; auto setter = [](float value) { qApp->getApplicationCompositor().setHmdUIAngularSize(value); }; - auto preference = new SpinnerPreference("HMD", "User Interface Horizontal Angular Size (degrees)", getter, setter); + auto preference = new SpinnerPreference("HMD", "UI horizontal angular size (degrees)", getter, setter); preference->setMin(30); preference->setMax(160); preference->setStep(1); @@ -310,7 +308,7 @@ void setupPreferences() { { auto getter = []()->float { return controller::InputDevice::getReticleMoveSpeed(); }; auto setter = [](float value) { controller::InputDevice::setReticleMoveSpeed(value); }; - auto preference = new SpinnerPreference("Sixense Controllers", "Reticle Movement Speed", getter, setter); + auto preference = new SpinnerPreference("Sixense Controllers", "Reticle movement speed", getter, setter); preference->setMin(0); preference->setMax(100); preference->setStep(1); @@ -325,7 +323,7 @@ void setupPreferences() { { auto getter = [ambientOcclusionConfig]()->QString { return ambientOcclusionConfig->getPreset(); }; auto setter = [ambientOcclusionConfig](QString preset) { ambientOcclusionConfig->setPreset(preset); }; - auto preference = new ComboBoxPreference(RENDER, "Ambient Occlusion", getter, setter); + auto preference = new ComboBoxPreference(RENDER, "Ambient occlusion", getter, setter); preference->setItems(ambientOcclusionConfig->getPresetList()); preferences->addPreference(preference); } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index e648a22650..0cba089256 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -11,8 +11,6 @@ #include "Base3DOverlay.h" -#include - #include #include @@ -41,103 +39,78 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : { } -void Base3DOverlay::setProperties(const QScriptValue& properties) { +void Base3DOverlay::setProperties(const QVariantMap& properties) { Overlay::setProperties(properties); - QScriptValue drawInFront = properties.property("drawInFront"); + auto drawInFront = properties["drawInFront"]; if (drawInFront.isValid()) { - bool value = drawInFront.toVariant().toBool(); + bool value = drawInFront.toBool(); setDrawInFront(value); } - QScriptValue position = properties.property("position"); + auto position = properties["position"]; // if "position" property was not there, check to see if they included aliases: point, p1 if (!position.isValid()) { - position = properties.property("p1"); + position = properties["p1"]; if (!position.isValid()) { - position = properties.property("point"); + position = properties["point"]; } } - if (position.isValid()) { - QScriptValue x = position.property("x"); - QScriptValue y = position.property("y"); - QScriptValue z = position.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newPosition; - newPosition.x = x.toVariant().toFloat(); - newPosition.y = y.toVariant().toFloat(); - newPosition.z = z.toVariant().toFloat(); - setPosition(newPosition); - } + setPosition(vec3FromVariant(position)); } - if (properties.property("lineWidth").isValid()) { - setLineWidth(properties.property("lineWidth").toVariant().toFloat()); + if (properties["lineWidth"].isValid()) { + setLineWidth(properties["lineWidth"].toFloat()); } - QScriptValue rotation = properties.property("rotation"); + auto rotation = properties["rotation"]; if (rotation.isValid()) { - glm::quat newRotation; - - // size, scale, dimensions is special, it might just be a single scalar, or it might be a vector, check that here - QScriptValue x = rotation.property("x"); - QScriptValue y = rotation.property("y"); - QScriptValue z = rotation.property("z"); - QScriptValue w = rotation.property("w"); - - - if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { - newRotation.x = x.toVariant().toFloat(); - newRotation.y = y.toVariant().toFloat(); - newRotation.z = z.toVariant().toFloat(); - newRotation.w = w.toVariant().toFloat(); - setRotation(newRotation); - } + setRotation(quatFromVariant(rotation)); } - if (properties.property("isSolid").isValid()) { - setIsSolid(properties.property("isSolid").toVariant().toBool()); + if (properties["isSolid"].isValid()) { + setIsSolid(properties["isSolid"].toBool()); } - if (properties.property("isFilled").isValid()) { - setIsSolid(properties.property("isSolid").toVariant().toBool()); + if (properties["isFilled"].isValid()) { + setIsSolid(properties["isSolid"].toBool()); } - if (properties.property("isWire").isValid()) { - setIsSolid(!properties.property("isWire").toVariant().toBool()); + if (properties["isWire"].isValid()) { + setIsSolid(!properties["isWire"].toBool()); } - if (properties.property("solid").isValid()) { - setIsSolid(properties.property("solid").toVariant().toBool()); + if (properties["solid"].isValid()) { + setIsSolid(properties["solid"].toBool()); } - if (properties.property("filled").isValid()) { - setIsSolid(properties.property("filled").toVariant().toBool()); + if (properties["filled"].isValid()) { + setIsSolid(properties["filled"].toBool()); } - if (properties.property("wire").isValid()) { - setIsSolid(!properties.property("wire").toVariant().toBool()); + if (properties["wire"].isValid()) { + setIsSolid(!properties["wire"].toBool()); } - if (properties.property("isDashedLine").isValid()) { - setIsDashedLine(properties.property("isDashedLine").toVariant().toBool()); + if (properties["isDashedLine"].isValid()) { + setIsDashedLine(properties["isDashedLine"].toBool()); } - if (properties.property("dashed").isValid()) { - setIsDashedLine(properties.property("dashed").toVariant().toBool()); + if (properties["dashed"].isValid()) { + setIsDashedLine(properties["dashed"].toBool()); } - if (properties.property("ignoreRayIntersection").isValid()) { - setIgnoreRayIntersection(properties.property("ignoreRayIntersection").toVariant().toBool()); + if (properties["ignoreRayIntersection"].isValid()) { + setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool()); } } -QScriptValue Base3DOverlay::getProperty(const QString& property) { +QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "position" || property == "start" || property == "p1" || property == "point") { - return vec3toScriptValue(_scriptEngine, getPosition()); + return vec3toVariant(getPosition()); } if (property == "lineWidth") { return _lineWidth; } if (property == "rotation") { - return quatToScriptValue(_scriptEngine, getRotation()); + return quatToVariant(getRotation()); } if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filed") { return _isSolid; diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 791d08109f..c001d39f69 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -52,8 +52,8 @@ public: virtual AABox getBounds() const = 0; - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); diff --git a/interface/src/ui/overlays/Billboard3DOverlay.cpp b/interface/src/ui/overlays/Billboard3DOverlay.cpp index 908676e0eb..182e7da978 100644 --- a/interface/src/ui/overlays/Billboard3DOverlay.cpp +++ b/interface/src/ui/overlays/Billboard3DOverlay.cpp @@ -18,19 +18,19 @@ Billboard3DOverlay::Billboard3DOverlay(const Billboard3DOverlay* billboard3DOver { } -void Billboard3DOverlay::setProperties(const QScriptValue &properties) { +void Billboard3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); PanelAttachable::setProperties(properties); Billboardable::setProperties(properties); } -QScriptValue Billboard3DOverlay::getProperty(const QString &property) { - QScriptValue value; - value = Billboardable::getProperty(_scriptEngine, property); +QVariant Billboard3DOverlay::getProperty(const QString &property) { + QVariant value; + value = Billboardable::getProperty(property); if (value.isValid()) { return value; } - value = PanelAttachable::getProperty(_scriptEngine, property); + value = PanelAttachable::getProperty(property); if (value.isValid()) { return value; } diff --git a/interface/src/ui/overlays/Billboard3DOverlay.h b/interface/src/ui/overlays/Billboard3DOverlay.h index 2f5af1068f..1d4d60bf5b 100644 --- a/interface/src/ui/overlays/Billboard3DOverlay.h +++ b/interface/src/ui/overlays/Billboard3DOverlay.h @@ -23,8 +23,8 @@ public: Billboard3DOverlay() {} Billboard3DOverlay(const Billboard3DOverlay* billboard3DOverlay); - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; protected: virtual void applyTransformTo(Transform& transform, bool force = false); diff --git a/interface/src/ui/overlays/Billboardable.cpp b/interface/src/ui/overlays/Billboardable.cpp index 997cf27609..a6226a46b0 100644 --- a/interface/src/ui/overlays/Billboardable.cpp +++ b/interface/src/ui/overlays/Billboardable.cpp @@ -14,18 +14,18 @@ #include #include -void Billboardable::setProperties(const QScriptValue &properties) { - QScriptValue isFacingAvatar = properties.property("isFacingAvatar"); +void Billboardable::setProperties(const QVariantMap& properties) { + auto isFacingAvatar = properties["isFacingAvatar"]; if (isFacingAvatar.isValid()) { - setIsFacingAvatar(isFacingAvatar.toVariant().toBool()); + setIsFacingAvatar(isFacingAvatar.toBool()); } } -QScriptValue Billboardable::getProperty(QScriptEngine* scriptEngine, const QString &property) { +QVariant Billboardable::getProperty(const QString &property) { if (property == "isFacingAvatar") { return isFacingAvatar(); } - return QScriptValue(); + return QVariant(); } void Billboardable::pointTransformAtCamera(Transform& transform, glm::quat offsetRotation) { diff --git a/interface/src/ui/overlays/Billboardable.h b/interface/src/ui/overlays/Billboardable.h index 82387ad703..e2d29a2769 100644 --- a/interface/src/ui/overlays/Billboardable.h +++ b/interface/src/ui/overlays/Billboardable.h @@ -12,11 +12,9 @@ #ifndef hifi_Billboardable_h #define hifi_Billboardable_h -#include - #include +#include -class QScriptEngine; class QString; class Transform; @@ -26,8 +24,8 @@ public: void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; } protected: - void setProperties(const QScriptValue& properties); - QScriptValue getProperty(QScriptEngine* scriptEngine, const QString& property); + void setProperties(const QVariantMap& properties); + QVariant getProperty(const QString& property); void pointTransformAtCamera(Transform& transform, glm::quat offsetRotation = {1, 0, 0, 0}); diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index a3ecbe7137..8fb3a36919 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -286,83 +286,76 @@ const render::ShapeKey Circle3DOverlay::getShapeKey() { return builder.build(); } -void Circle3DOverlay::setProperties(const QScriptValue &properties) { +void Circle3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); - QScriptValue startAt = properties.property("startAt"); + QVariant startAt = properties["startAt"]; if (startAt.isValid()) { - setStartAt(startAt.toVariant().toFloat()); + setStartAt(startAt.toFloat()); } - QScriptValue endAt = properties.property("endAt"); + QVariant endAt = properties["endAt"]; if (endAt.isValid()) { - setEndAt(endAt.toVariant().toFloat()); + setEndAt(endAt.toFloat()); } - QScriptValue outerRadius = properties.property("radius"); + QVariant outerRadius = properties["radius"]; if (!outerRadius.isValid()) { - outerRadius = properties.property("outerRadius"); + outerRadius = properties["outerRadius"]; } if (outerRadius.isValid()) { - setOuterRadius(outerRadius.toVariant().toFloat()); + setOuterRadius(outerRadius.toFloat()); } - QScriptValue innerRadius = properties.property("innerRadius"); + QVariant innerRadius = properties["innerRadius"]; if (innerRadius.isValid()) { - setInnerRadius(innerRadius.toVariant().toFloat()); + setInnerRadius(innerRadius.toFloat()); } - QScriptValue hasTickMarks = properties.property("hasTickMarks"); + QVariant hasTickMarks = properties["hasTickMarks"]; if (hasTickMarks.isValid()) { - setHasTickMarks(hasTickMarks.toVariant().toBool()); + setHasTickMarks(hasTickMarks.toBool()); } - QScriptValue majorTickMarksAngle = properties.property("majorTickMarksAngle"); + QVariant majorTickMarksAngle = properties["majorTickMarksAngle"]; if (majorTickMarksAngle.isValid()) { - setMajorTickMarksAngle(majorTickMarksAngle.toVariant().toFloat()); + setMajorTickMarksAngle(majorTickMarksAngle.toFloat()); } - QScriptValue minorTickMarksAngle = properties.property("minorTickMarksAngle"); + QVariant minorTickMarksAngle = properties["minorTickMarksAngle"]; if (minorTickMarksAngle.isValid()) { - setMinorTickMarksAngle(minorTickMarksAngle.toVariant().toFloat()); + setMinorTickMarksAngle(minorTickMarksAngle.toFloat()); } - QScriptValue majorTickMarksLength = properties.property("majorTickMarksLength"); + QVariant majorTickMarksLength = properties["majorTickMarksLength"]; if (majorTickMarksLength.isValid()) { - setMajorTickMarksLength(majorTickMarksLength.toVariant().toFloat()); + setMajorTickMarksLength(majorTickMarksLength.toFloat()); } - QScriptValue minorTickMarksLength = properties.property("minorTickMarksLength"); + QVariant minorTickMarksLength = properties["minorTickMarksLength"]; if (minorTickMarksLength.isValid()) { - setMinorTickMarksLength(minorTickMarksLength.toVariant().toFloat()); + setMinorTickMarksLength(minorTickMarksLength.toFloat()); } - QScriptValue majorTickMarksColor = properties.property("majorTickMarksColor"); + bool valid; + auto majorTickMarksColor = properties["majorTickMarksColor"]; if (majorTickMarksColor.isValid()) { - QScriptValue red = majorTickMarksColor.property("red"); - QScriptValue green = majorTickMarksColor.property("green"); - QScriptValue blue = majorTickMarksColor.property("blue"); - if (red.isValid() && green.isValid() && blue.isValid()) { - _majorTickMarksColor.red = red.toVariant().toInt(); - _majorTickMarksColor.green = green.toVariant().toInt(); - _majorTickMarksColor.blue = blue.toVariant().toInt(); + auto color = xColorFromVariant(majorTickMarksColor, valid); + if (valid) { + _majorTickMarksColor = color; } } - QScriptValue minorTickMarksColor = properties.property("minorTickMarksColor"); + auto minorTickMarksColor = properties["minorTickMarksColor"]; if (minorTickMarksColor.isValid()) { - QScriptValue red = minorTickMarksColor.property("red"); - QScriptValue green = minorTickMarksColor.property("green"); - QScriptValue blue = minorTickMarksColor.property("blue"); - if (red.isValid() && green.isValid() && blue.isValid()) { - _minorTickMarksColor.red = red.toVariant().toInt(); - _minorTickMarksColor.green = green.toVariant().toInt(); - _minorTickMarksColor.blue = blue.toVariant().toInt(); + auto color = xColorFromVariant(majorTickMarksColor, valid); + if (valid) { + _minorTickMarksColor = color; } } } -QScriptValue Circle3DOverlay::getProperty(const QString& property) { +QVariant Circle3DOverlay::getProperty(const QString& property) { if (property == "startAt") { return _startAt; } @@ -394,10 +387,10 @@ QScriptValue Circle3DOverlay::getProperty(const QString& property) { return _minorTickMarksLength; } if (property == "majorTickMarksColor") { - return xColorToScriptValue(_scriptEngine, _majorTickMarksColor); + return xColorToVariant(_majorTickMarksColor); } if (property == "minorTickMarksColor") { - return xColorToScriptValue(_scriptEngine, _minorTickMarksColor); + return xColorToVariant(_minorTickMarksColor); } return Planar3DOverlay::getProperty(property); diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 7050da3368..f0bc49c313 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -26,8 +26,8 @@ public: virtual void render(RenderArgs* args); virtual const render::ShapeKey getShapeKey() override; - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; float getStartAt() const { return _startAt; } float getEndAt() const { return _endAt; } diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 8ba29bd336..38650f9fda 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -11,8 +11,6 @@ // include this before QGLWidget, which includes an earlier version of OpenGL #include "Cube3DOverlay.h" -#include - #include #include #include @@ -110,18 +108,18 @@ Cube3DOverlay* Cube3DOverlay::createClone() const { return new Cube3DOverlay(this); } -void Cube3DOverlay::setProperties(const QScriptValue& properties) { +void Cube3DOverlay::setProperties(const QVariantMap& properties) { Volume3DOverlay::setProperties(properties); - QScriptValue borderSize = properties.property("borderSize"); + auto borderSize = properties["borderSize"]; if (borderSize.isValid()) { - float value = borderSize.toVariant().toFloat(); + float value = borderSize.toFloat(); setBorderSize(value); } } -QScriptValue Cube3DOverlay::getProperty(const QString& property) { +QVariant Cube3DOverlay::getProperty(const QString& property) { if (property == "borderSize") { return _borderSize; } diff --git a/interface/src/ui/overlays/Cube3DOverlay.h b/interface/src/ui/overlays/Cube3DOverlay.h index 30b6646362..93ea46db4d 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.h +++ b/interface/src/ui/overlays/Cube3DOverlay.h @@ -32,8 +32,8 @@ public: void setBorderSize(float value) { _borderSize = value; } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; private: float _borderSize; diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 817e71a4da..39106ad61e 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -11,10 +11,7 @@ #include "Grid3DOverlay.h" -#include - #include - #include #include #include @@ -92,24 +89,24 @@ const render::ShapeKey Grid3DOverlay::getShapeKey() { return render::ShapeKey::Builder().withOwnPipeline(); } -void Grid3DOverlay::setProperties(const QScriptValue& properties) { +void Grid3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); - if (properties.property("followCamera").isValid()) { - _followCamera = properties.property("followCamera").toVariant().toBool(); + if (properties["followCamera"].isValid()) { + _followCamera = properties["followCamera"].toBool(); } - if (properties.property("majorGridEvery").isValid()) { - _majorGridEvery = properties.property("majorGridEvery").toVariant().toInt(); + if (properties["majorGridEvery"].isValid()) { + _majorGridEvery = properties["majorGridEvery"].toInt(); } - if (properties.property("minorGridEvery").isValid()) { - _minorGridEvery = properties.property("minorGridEvery").toVariant().toFloat(); + if (properties["minorGridEvery"].isValid()) { + _minorGridEvery = properties["minorGridEvery"].toFloat(); } updateGrid(); } -QScriptValue Grid3DOverlay::getProperty(const QString& property) { +QVariant Grid3DOverlay::getProperty(const QString& property) { if (property == "followCamera") { return _followCamera; } diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h index 3614a13000..aa372436de 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.h +++ b/interface/src/ui/overlays/Grid3DOverlay.h @@ -28,8 +28,8 @@ public: virtual void render(RenderArgs* args); virtual const render::ShapeKey getShapeKey() override; - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual Grid3DOverlay* createClone() const; diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 66f7143e83..fe2fc5ddcd 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -12,8 +12,6 @@ #include "Image3DOverlay.h" -#include - #include #include #include @@ -114,41 +112,42 @@ const render::ShapeKey Image3DOverlay::getShapeKey() { return builder.build(); } -void Image3DOverlay::setProperties(const QScriptValue &properties) { +void Image3DOverlay::setProperties(const QVariantMap& properties) { Billboard3DOverlay::setProperties(properties); - QScriptValue urlValue = properties.property("url"); + auto urlValue = properties["url"]; if (urlValue.isValid()) { - QString newURL = urlValue.toVariant().toString(); + QString newURL = urlValue.toString(); if (newURL != _url) { setURL(newURL); } } - QScriptValue subImageBounds = properties.property("subImage"); - if (subImageBounds.isValid()) { - if (subImageBounds.isNull()) { + auto subImageBoundsVar = properties["subImage"]; + if (subImageBoundsVar.isValid()) { + if (subImageBoundsVar.isNull()) { _fromImage = QRect(); } else { QRect oldSubImageRect = _fromImage; QRect subImageRect = _fromImage; - if (subImageBounds.property("x").isValid()) { - subImageRect.setX(subImageBounds.property("x").toVariant().toInt()); + auto subImageBounds = subImageBoundsVar.toMap(); + if (subImageBounds["x"].isValid()) { + subImageRect.setX(subImageBounds["x"].toInt()); } else { subImageRect.setX(oldSubImageRect.x()); } - if (subImageBounds.property("y").isValid()) { - subImageRect.setY(subImageBounds.property("y").toVariant().toInt()); + if (subImageBounds["y"].isValid()) { + subImageRect.setY(subImageBounds["y"].toInt()); } else { subImageRect.setY(oldSubImageRect.y()); } - if (subImageBounds.property("width").isValid()) { - subImageRect.setWidth(subImageBounds.property("width").toVariant().toInt()); + if (subImageBounds["width"].isValid()) { + subImageRect.setWidth(subImageBounds["width"].toInt()); } else { subImageRect.setWidth(oldSubImageRect.width()); } - if (subImageBounds.property("height").isValid()) { - subImageRect.setHeight(subImageBounds.property("height").toVariant().toInt()); + if (subImageBounds["height"].isValid()) { + subImageRect.setHeight(subImageBounds["height"].toInt()); } else { subImageRect.setHeight(oldSubImageRect.height()); } @@ -156,21 +155,21 @@ void Image3DOverlay::setProperties(const QScriptValue &properties) { } } - QScriptValue emissiveValue = properties.property("emissive"); + auto emissiveValue = properties["emissive"]; if (emissiveValue.isValid()) { _emissive = emissiveValue.toBool(); } } -QScriptValue Image3DOverlay::getProperty(const QString& property) { +QVariant Image3DOverlay::getProperty(const QString& property) { if (property == "url") { return _url; } if (property == "subImage") { - return qRectToScriptValue(_scriptEngine, _fromImage); + return _fromImage; } if (property == "offsetPosition") { - return vec3toScriptValue(_scriptEngine, getOffsetPosition()); + return vec3toVariant(getOffsetPosition()); } if (property == "emissive") { return _emissive; diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index 0b0b245872..2bdc58709a 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -37,8 +37,8 @@ public: void setURL(const QString& url); void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index c0a1c9e282..3971e91211 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -64,56 +64,41 @@ void Line3DOverlay::render(RenderArgs* args) { } const render::ShapeKey Line3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder(); + auto builder = render::ShapeKey::Builder().withoutCullFace(); if (getAlpha() != 1.0f) { builder.withTranslucent(); } return builder.build(); } -void Line3DOverlay::setProperties(const QScriptValue& properties) { +void Line3DOverlay::setProperties(const QVariantMap& properties) { Base3DOverlay::setProperties(properties); - QScriptValue start = properties.property("start"); + auto start = properties["start"]; // if "start" property was not there, check to see if they included aliases: startPoint if (!start.isValid()) { - start = properties.property("startPoint"); + start = properties["startPoint"]; } if (start.isValid()) { - QScriptValue x = start.property("x"); - QScriptValue y = start.property("y"); - QScriptValue z = start.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newStart; - newStart.x = x.toVariant().toFloat(); - newStart.y = y.toVariant().toFloat(); - newStart.z = z.toVariant().toFloat(); - setStart(newStart); - } + setStart(vec3FromVariant(start)); } - QScriptValue end = properties.property("end"); + auto end = properties["end"]; // if "end" property was not there, check to see if they included aliases: endPoint if (!end.isValid()) { - end = properties.property("endPoint"); + end = properties["endPoint"]; } if (end.isValid()) { - QScriptValue x = end.property("x"); - QScriptValue y = end.property("y"); - QScriptValue z = end.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newEnd; - newEnd.x = x.toVariant().toFloat(); - newEnd.y = y.toVariant().toFloat(); - newEnd.z = z.toVariant().toFloat(); - setEnd(newEnd); - } + setEnd(vec3FromVariant(end)); } } -QScriptValue Line3DOverlay::getProperty(const QString& property) { +QVariant Line3DOverlay::getProperty(const QString& property) { + if (property == "start" || property == "startPoint" || property == "p1") { + return vec3toVariant(_start); + } if (property == "end" || property == "endPoint" || property == "p2") { - return vec3toScriptValue(_scriptEngine, _end); + return vec3toVariant(_end); } return Base3DOverlay::getProperty(property); diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h index b40ff78231..8ff38859a2 100644 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ b/interface/src/ui/overlays/Line3DOverlay.h @@ -35,8 +35,8 @@ public: void setStart(const glm::vec3& start) { _start = start; } void setEnd(const glm::vec3& end) { _end = end; } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual Line3DOverlay* createClone() const; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index a06a9b0605..010698800b 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -84,7 +84,7 @@ void ModelOverlay::render(RenderArgs* args) { } } -void ModelOverlay::setProperties(const QScriptValue &properties) { +void ModelOverlay::setProperties(const QVariantMap& properties) { auto position = getPosition(); auto rotation = getRotation(); auto scale = getDimensions(); @@ -105,16 +105,16 @@ void ModelOverlay::setProperties(const QScriptValue &properties) { } } - QScriptValue urlValue = properties.property("url"); - if (urlValue.isValid() && urlValue.isString()) { + auto urlValue = properties["url"]; + if (urlValue.isValid() && urlValue.canConvert()) { _url = urlValue.toString(); _updateModel = true; _isLoaded = false; } - QScriptValue texturesValue = properties.property("textures"); - if (texturesValue.isValid() && texturesValue.toVariant().canConvert(QVariant::Map)) { - QVariantMap textureMap = texturesValue.toVariant().toMap(); + auto texturesValue = properties["textures"]; + if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) { + QVariantMap textureMap = texturesValue.toMap(); foreach(const QString& key, textureMap.keys()) { QUrl newTextureURL = textureMap[key].toUrl(); @@ -129,22 +129,22 @@ void ModelOverlay::setProperties(const QScriptValue &properties) { } } -QScriptValue ModelOverlay::getProperty(const QString& property) { +QVariant ModelOverlay::getProperty(const QString& property) { if (property == "url") { return _url.toString(); } if (property == "dimensions" || property == "scale" || property == "size") { - return vec3toScriptValue(_scriptEngine, _model.getScaleToFitDimensions()); + return vec3toVariant(_model.getScaleToFitDimensions()); } if (property == "textures") { if (_modelTextures.size() > 0) { - QScriptValue textures = _scriptEngine->newObject(); + QVariantMap textures; foreach(const QString& key, _modelTextures.keys()) { - textures.setProperty(key, _modelTextures[key].toString()); + textures[key] = _modelTextures[key].toString(); } return textures; } else { - return QScriptValue(); + return QVariant(); } } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 5d09ea75a0..0b67f7ed37 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -27,9 +27,9 @@ public: virtual void update(float deltatime); virtual void render(RenderArgs* args); - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo); diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 64b5e2aedf..a53daf7d14 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -52,70 +52,68 @@ Overlay::Overlay(const Overlay* overlay) : _colorPulse(overlay->_colorPulse), _color(overlay->_color), _visible(overlay->_visible), - _anchor(overlay->_anchor), - _scriptEngine(NULL) + _anchor(overlay->_anchor) { } -void Overlay::init(QScriptEngine* scriptEngine) { - _scriptEngine = scriptEngine; -} - Overlay::~Overlay() { } -void Overlay::setProperties(const QScriptValue& properties) { - QScriptValue color = properties.property("color"); - xColorFromScriptValue(properties.property("color"), _color); +void Overlay::setProperties(const QVariantMap& properties) { + bool valid; + auto color = xColorFromVariant(properties["color"], valid); + if (valid) { + _color = color; + } - if (properties.property("alpha").isValid()) { - setAlpha(properties.property("alpha").toVariant().toFloat()); + if (properties["alpha"].isValid()) { + setAlpha(properties["alpha"].toFloat()); } - if (properties.property("glowLevel").isValid()) { - setGlowLevel(properties.property("glowLevel").toVariant().toFloat()); + if (properties["glowLevel"].isValid()) { + setGlowLevel(properties["glowLevel"].toFloat()); } - if (properties.property("pulseMax").isValid()) { - setPulseMax(properties.property("pulseMax").toVariant().toFloat()); + if (properties["pulseMax"].isValid()) { + setPulseMax(properties["pulseMax"].toFloat()); } - if (properties.property("pulseMin").isValid()) { - setPulseMin(properties.property("pulseMin").toVariant().toFloat()); + if (properties["pulseMin"].isValid()) { + setPulseMin(properties["pulseMin"].toFloat()); } - if (properties.property("pulsePeriod").isValid()) { - setPulsePeriod(properties.property("pulsePeriod").toVariant().toFloat()); + if (properties["pulsePeriod"].isValid()) { + setPulsePeriod(properties["pulsePeriod"].toFloat()); } - if (properties.property("glowLevelPulse").isValid()) { - setGlowLevelPulse(properties.property("glowLevelPulse").toVariant().toFloat()); + if (properties["glowLevelPulse"].isValid()) { + setGlowLevelPulse(properties["glowLevelPulse"].toFloat()); } - if (properties.property("alphaPulse").isValid()) { - setAlphaPulse(properties.property("alphaPulse").toVariant().toFloat()); + if (properties["alphaPulse"].isValid()) { + setAlphaPulse(properties["alphaPulse"].toFloat()); } - if (properties.property("colorPulse").isValid()) { - setColorPulse(properties.property("colorPulse").toVariant().toFloat()); + if (properties["colorPulse"].isValid()) { + setColorPulse(properties["colorPulse"].toFloat()); } - if (properties.property("visible").isValid()) { - bool visible = properties.property("visible").toVariant().toBool(); + if (properties["visible"].isValid()) { + bool visible = properties["visible"].toBool(); setVisible(visible); } - if (properties.property("anchor").isValid()) { - QString property = properties.property("anchor").toVariant().toString(); + if (properties["anchor"].isValid()) { + QString property = properties["anchor"].toString(); if (property == "MyAvatar") { setAnchor(MY_AVATAR); } } } -QScriptValue Overlay::getProperty(const QString& property) { +QVariant Overlay::getProperty(const QString& property) { if (property == "color") { - return xColorToScriptValue(_scriptEngine, _color); + return xColorToVariant(_color); } if (property == "alpha") { return _alpha; @@ -148,7 +146,7 @@ QScriptValue Overlay::getProperty(const QString& property) { return _anchor == MY_AVATAR ? "MyAvatar" : ""; } - return QScriptValue(); + return QVariant(); } xColor Overlay::getColor() { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 84e9c1bb59..a2cf7a30f2 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -15,9 +15,6 @@ #include // for xColor #include -class QScriptEngine; -class QScriptValue; - class Overlay : public QObject { Q_OBJECT @@ -34,7 +31,6 @@ public: Overlay(); Overlay(const Overlay* overlay); ~Overlay(); - void init(QScriptEngine* scriptEngine); virtual void update(float deltatime) {} virtual void render(RenderArgs* args) = 0; @@ -82,9 +78,9 @@ public: void setColorPulse(float value) { _colorPulse = value; } void setAlphaPulse(float value) { _alphaPulse = value; } - virtual void setProperties(const QScriptValue& properties); + virtual void setProperties(const QVariantMap& properties); virtual Overlay* createClone() const = 0; - virtual QScriptValue getProperty(const QString& property); + virtual QVariant getProperty(const QString& property); render::ItemID getRenderItemID() const { return _renderItemID; } void setRenderItemID(render::ItemID renderItemID) { _renderItemID = renderItemID; } @@ -112,8 +108,6 @@ protected: xColor _color; bool _visible; // should the overlay be drawn at all Anchor _anchor; - - QScriptEngine* _scriptEngine; }; namespace render { diff --git a/interface/src/ui/overlays/Overlay2D.cpp b/interface/src/ui/overlays/Overlay2D.cpp index e37e70700c..d5f88a84d7 100644 --- a/interface/src/ui/overlays/Overlay2D.cpp +++ b/interface/src/ui/overlays/Overlay2D.cpp @@ -23,49 +23,44 @@ AABox Overlay2D::getBounds() const { glm::vec3(_bounds.width(), _bounds.height(), 0.01f)); } -void Overlay2D::setProperties(const QScriptValue& properties) { +void Overlay2D::setProperties(const QVariantMap& properties) { Overlay::setProperties(properties); - QScriptValue bounds = properties.property("bounds"); + auto bounds = properties["bounds"]; if (bounds.isValid()) { - QRect boundsRect; - boundsRect.setX(bounds.property("x").toVariant().toInt()); - boundsRect.setY(bounds.property("y").toVariant().toInt()); - boundsRect.setWidth(bounds.property("width").toVariant().toInt()); - boundsRect.setHeight(bounds.property("height").toVariant().toInt()); - setBounds(boundsRect); + bool valid; + auto rect = qRectFromVariant(bounds, valid); + setBounds(rect); } else { QRect oldBounds = _bounds; QRect newBounds = oldBounds; - - if (properties.property("x").isValid()) { - newBounds.setX(properties.property("x").toVariant().toInt()); + if (properties["x"].isValid()) { + newBounds.setX(properties["x"].toInt()); } else { newBounds.setX(oldBounds.x()); } - if (properties.property("y").isValid()) { - newBounds.setY(properties.property("y").toVariant().toInt()); + if (properties["y"].isValid()) { + newBounds.setY(properties["y"].toInt()); } else { newBounds.setY(oldBounds.y()); } - if (properties.property("width").isValid()) { - newBounds.setWidth(properties.property("width").toVariant().toInt()); + if (properties["width"].isValid()) { + newBounds.setWidth(properties["width"].toInt()); } else { newBounds.setWidth(oldBounds.width()); } - if (properties.property("height").isValid()) { - newBounds.setHeight(properties.property("height").toVariant().toInt()); + if (properties["height"].isValid()) { + newBounds.setHeight(properties["height"].toInt()); } else { newBounds.setHeight(oldBounds.height()); } setBounds(newBounds); - //qDebug() << "set bounds to " << getBounds(); } } -QScriptValue Overlay2D::getProperty(const QString& property) { +QVariant Overlay2D::getProperty(const QString& property) { if (property == "bounds") { - return qRectToScriptValue(_scriptEngine, _bounds); + return qRectToVariant(_bounds); } if (property == "x") { return _bounds.x(); diff --git a/interface/src/ui/overlays/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h index 382105c047..3fd11f79f0 100644 --- a/interface/src/ui/overlays/Overlay2D.h +++ b/interface/src/ui/overlays/Overlay2D.h @@ -40,8 +40,8 @@ public: void setHeight(int height) { _bounds.setHeight(height); } void setBounds(const QRect& bounds) { _bounds = bounds; } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; protected: QRect _bounds; // where on the screen to draw diff --git a/interface/src/ui/overlays/OverlayPanel.cpp b/interface/src/ui/overlays/OverlayPanel.cpp index 7db2c6f8a2..cb57c6ec6b 100644 --- a/interface/src/ui/overlays/OverlayPanel.cpp +++ b/interface/src/ui/overlays/OverlayPanel.cpp @@ -26,26 +26,27 @@ PropertyBinding::PropertyBinding(QString avatar, QUuid entity) : { } -QScriptValue propertyBindingToScriptValue(QScriptEngine* engine, const PropertyBinding& value) { - QScriptValue obj = engine->newObject(); +QVariant propertyBindingToVariant(const PropertyBinding& value) { + QVariantMap obj; if (value.avatar == "MyAvatar") { - obj.setProperty("avatar", "MyAvatar"); + obj["avatar"] = "MyAvatar"; } else if (!value.entity.isNull()) { - obj.setProperty("entity", engine->newVariant(value.entity)); + obj["entity"] = value.entity; } return obj; } -void propertyBindingFromScriptValue(const QScriptValue& object, PropertyBinding& value) { - QScriptValue avatar = object.property("avatar"); - QScriptValue entity = object.property("entity"); +void propertyBindingFromVariant(const QVariant& objectVar, PropertyBinding& value) { + auto object = objectVar.toMap(); + auto avatar = object["avatar"]; + auto entity = object["entity"]; if (avatar.isValid() && !avatar.isNull()) { - value.avatar = avatar.toVariant().toString(); + value.avatar = avatar.toString(); } else if (entity.isValid() && !entity.isNull()) { - value.entity = entity.toVariant().toUuid(); + value.entity = entity.toUuid(); } } @@ -62,103 +63,82 @@ void OverlayPanel::removeChild(unsigned int childId) { } } -QScriptValue OverlayPanel::getProperty(const QString &property) { +QVariant OverlayPanel::getProperty(const QString &property) { if (property == "anchorPosition") { - return vec3toScriptValue(_scriptEngine, getAnchorPosition()); + return vec3toVariant(getAnchorPosition()); } if (property == "anchorPositionBinding") { - return propertyBindingToScriptValue(_scriptEngine, - PropertyBinding(_anchorPositionBindMyAvatar ? + return propertyBindingToVariant(PropertyBinding(_anchorPositionBindMyAvatar ? "MyAvatar" : "", _anchorPositionBindEntity)); } if (property == "anchorRotation") { - return quatToScriptValue(_scriptEngine, getAnchorRotation()); + return quatToVariant(getAnchorRotation()); } if (property == "anchorRotationBinding") { - return propertyBindingToScriptValue(_scriptEngine, - PropertyBinding(_anchorRotationBindMyAvatar ? + return propertyBindingToVariant(PropertyBinding(_anchorRotationBindMyAvatar ? "MyAvatar" : "", _anchorRotationBindEntity)); } if (property == "anchorScale") { - return vec3toScriptValue(_scriptEngine, getAnchorScale()); + return vec3toVariant(getAnchorScale()); } if (property == "visible") { return getVisible(); } if (property == "children") { - QScriptValue array = _scriptEngine->newArray(_children.length()); + QVariantList array; for (int i = 0; i < _children.length(); i++) { - array.setProperty(i, _children[i]); + array.append(_children[i]); } return array; } - QScriptValue value = Billboardable::getProperty(_scriptEngine, property); + auto value = Billboardable::getProperty(property); if (value.isValid()) { return value; } - return PanelAttachable::getProperty(_scriptEngine, property); + return PanelAttachable::getProperty(property); } -void OverlayPanel::setProperties(const QScriptValue &properties) { +void OverlayPanel::setProperties(const QVariantMap& properties) { PanelAttachable::setProperties(properties); Billboardable::setProperties(properties); - QScriptValue anchorPosition = properties.property("anchorPosition"); - if (anchorPosition.isValid() && - anchorPosition.property("x").isValid() && - anchorPosition.property("y").isValid() && - anchorPosition.property("z").isValid()) { - glm::vec3 newPosition; - vec3FromScriptValue(anchorPosition, newPosition); - setAnchorPosition(newPosition); + auto anchorPosition = properties["anchorPosition"]; + if (anchorPosition.isValid()) { + setAnchorPosition(vec3FromVariant(anchorPosition)); } - QScriptValue anchorPositionBinding = properties.property("anchorPositionBinding"); + auto anchorPositionBinding = properties["anchorPositionBinding"]; if (anchorPositionBinding.isValid()) { PropertyBinding binding = {}; - propertyBindingFromScriptValue(anchorPositionBinding, binding); + propertyBindingFromVariant(anchorPositionBinding, binding); _anchorPositionBindMyAvatar = binding.avatar == "MyAvatar"; _anchorPositionBindEntity = binding.entity; } - QScriptValue anchorRotation = properties.property("anchorRotation"); - if (anchorRotation.isValid() && - anchorRotation.property("x").isValid() && - anchorRotation.property("y").isValid() && - anchorRotation.property("z").isValid() && - anchorRotation.property("w").isValid()) { - glm::quat newRotation; - quatFromScriptValue(anchorRotation, newRotation); - setAnchorRotation(newRotation); + auto anchorRotation = properties["anchorRotation"]; + if (anchorRotation.isValid()) { + setAnchorRotation(quatFromVariant(anchorRotation)); } - QScriptValue anchorRotationBinding = properties.property("anchorRotationBinding"); + auto anchorRotationBinding = properties["anchorRotationBinding"]; if (anchorRotationBinding.isValid()) { PropertyBinding binding = {}; - propertyBindingFromScriptValue(anchorPositionBinding, binding); + propertyBindingFromVariant(anchorPositionBinding, binding); _anchorRotationBindMyAvatar = binding.avatar == "MyAvatar"; _anchorRotationBindEntity = binding.entity; } - QScriptValue anchorScale = properties.property("anchorScale"); + auto anchorScale = properties["anchorScale"]; if (anchorScale.isValid()) { - if (anchorScale.property("x").isValid() && - anchorScale.property("y").isValid() && - anchorScale.property("z").isValid()) { - glm::vec3 newScale; - vec3FromScriptValue(anchorScale, newScale); - setAnchorScale(newScale); - } else { - setAnchorScale(anchorScale.toVariant().toFloat()); - } + setAnchorScale(vec3FromVariant(anchorScale)); } - QScriptValue visible = properties.property("visible"); + auto visible = properties["visible"]; if (visible.isValid()) { - setVisible(visible.toVariant().toBool()); + setVisible(visible.toBool()); } } diff --git a/interface/src/ui/overlays/OverlayPanel.h b/interface/src/ui/overlays/OverlayPanel.h index 221763fe87..df553883f1 100644 --- a/interface/src/ui/overlays/OverlayPanel.h +++ b/interface/src/ui/overlays/OverlayPanel.h @@ -16,7 +16,6 @@ #include #include -#include #include #include "PanelAttachable.h" @@ -30,8 +29,8 @@ public: QUuid entity; }; -QScriptValue propertyBindingToScriptValue(QScriptEngine* engine, const PropertyBinding& value); -void propertyBindingFromScriptValue(const QScriptValue& object, PropertyBinding& value); +QVariant propertyBindingToVariant(const PropertyBinding& value); +void propertyBindingFromVariant(const QVariant& object, PropertyBinding& value); class OverlayPanel : public QObject, public PanelAttachable, public Billboardable { @@ -60,8 +59,8 @@ public: void removeChild(unsigned int childId); unsigned int popLastChild() { return _children.takeLast(); } - QScriptValue getProperty(const QString& property); - void setProperties(const QScriptValue& properties); + void setProperties(const QVariantMap& properties); + QVariant getProperty(const QString& property); virtual void applyTransformTo(Transform& transform, bool force = false); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5a2d05815b..f003b0316e 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -153,7 +153,7 @@ Overlay::Pointer Overlays::getOverlay(unsigned int id) const { return nullptr; } -unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) { +unsigned int Overlays::addOverlay(const QString& type, const QVariant& properties) { Overlay::Pointer thisOverlay = nullptr; if (type == ImageOverlay::TYPE) { @@ -187,15 +187,13 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope } if (thisOverlay) { - thisOverlay->setProperties(properties); + thisOverlay->setProperties(properties.toMap()); return addOverlay(thisOverlay); } return 0; } unsigned int Overlays::addOverlay(Overlay::Pointer overlay) { - overlay->init(_scriptEngine); - QWriteLocker lock(&_lock); unsigned int thisID = _nextOverlayID; _nextOverlayID++; @@ -228,12 +226,12 @@ unsigned int Overlays::cloneOverlay(unsigned int id) { return 0; // Not found } -bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) { +bool Overlays::editOverlay(unsigned int id, const QVariant& properties) { QWriteLocker lock(&_lock); Overlay::Pointer thisOverlay = getOverlay(id); if (thisOverlay) { - thisOverlay->setProperties(properties); + thisOverlay->setProperties(properties.toMap()); if (thisOverlay->is3D()) { auto itemID = thisOverlay->getRenderItemID(); @@ -376,36 +374,21 @@ OverlayPropertyResult Overlays::getProperty(unsigned int id, const QString& prop return result; } -OverlayPropertyResult::OverlayPropertyResult() : - value(QScriptValue()) -{ +OverlayPropertyResult::OverlayPropertyResult() { } -QScriptValue OverlayPropertyResultToScriptValue(QScriptEngine* engine, const OverlayPropertyResult& result) -{ - if (!result.value.isValid()) { +QScriptValue OverlayPropertyResultToScriptValue(QScriptEngine* engine, const OverlayPropertyResult& value) { + if (!value.value.isValid()) { return QScriptValue::UndefinedValue; } - - QScriptValue object = engine->newObject(); - if (result.value.isObject()) { - QScriptValueIterator it(result.value); - while (it.hasNext()) { - it.next(); - object.setProperty(it.name(), QScriptValue(it.value().toString())); - } - - } else { - object = result.value; - } - return object; + return engine->newVariant(value.value); } -void OverlayPropertyResultFromScriptValue(const QScriptValue& value, OverlayPropertyResult& result) -{ - result.value = value; +void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPropertyResult& value) { + value.value = object.toVariant(); } + RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray) { float bestDistance = std::numeric_limits::max(); bool bestIsFront = false; @@ -452,7 +435,7 @@ RayToOverlayIntersectionResult::RayToOverlayIntersectionResult() : } QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { - QScriptValue obj = engine->newObject(); + auto obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("overlayID", value.overlayID); obj.setProperty("distance", value.distance); @@ -484,18 +467,19 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, break; } obj.setProperty("face", faceName); - QScriptValue intersection = vec3toScriptValue(engine, value.intersection); + auto intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); obj.setProperty("extraInfo", value.extraInfo); return obj; } -void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { - value.intersects = object.property("intersects").toVariant().toBool(); - value.overlayID = object.property("overlayID").toVariant().toInt(); - value.distance = object.property("distance").toVariant().toFloat(); +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar, RayToOverlayIntersectionResult& value) { + QVariantMap object = objectVar.toVariant().toMap(); + value.intersects = object["intersects"].toBool(); + value.overlayID = object["overlayID"].toInt(); + value.distance = object["distance"].toFloat(); - QString faceName = object.property("face").toVariant().toString(); + QString faceName = object["face"].toString(); if (faceName == "MIN_X_FACE") { value.face = MIN_X_FACE; } else if (faceName == "MAX_X_FACE") { @@ -511,11 +495,15 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R } else { value.face = UNKNOWN_FACE; }; - QScriptValue intersection = object.property("intersection"); + auto intersection = object["intersection"]; if (intersection.isValid()) { - vec3FromScriptValue(intersection, value.intersection); + bool valid; + auto newIntersection = vec3FromVariant(intersection, valid); + if (valid) { + value.intersection = newIntersection; + } } - value.extraInfo = object.property("extraInfo").toVariant().toString(); + value.extraInfo = object["extraInfo"].toString(); } bool Overlays::isLoaded(unsigned int id) { @@ -552,16 +540,16 @@ unsigned int Overlays::addPanel(OverlayPanel::Pointer panel) { return thisID; } -unsigned int Overlays::addPanel(const QScriptValue& properties) { +unsigned int Overlays::addPanel(const QVariant& properties) { OverlayPanel::Pointer panel = std::make_shared(); panel->init(_scriptEngine); - panel->setProperties(properties); + panel->setProperties(properties.toMap()); return addPanel(panel); } -void Overlays::editPanel(unsigned int panelId, const QScriptValue& properties) { +void Overlays::editPanel(unsigned int panelId, const QVariant& properties) { if (_panels.contains(panelId)) { - _panels[panelId]->setProperties(properties); + _panels[panelId]->setProperties(properties.toMap()); } } @@ -609,4 +597,4 @@ float Overlays::width() const { float Overlays::height() const { auto offscreenUi = DependencyManager::get(); return offscreenUi->getWindow()->size().height(); -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 7bf6ce94eb..25ba00fdf0 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -31,7 +31,7 @@ class PickRay; class OverlayPropertyResult { public: OverlayPropertyResult(); - QScriptValue value; + QVariant value; }; Q_DECLARE_METATYPE(OverlayPropertyResult); @@ -75,7 +75,7 @@ public: public slots: /// adds an overlay with the specific properties - unsigned int addOverlay(const QString& type, const QScriptValue& properties); + unsigned int addOverlay(const QString& type, const QVariant& properties); /// adds an overlay that's already been created unsigned int addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } @@ -86,7 +86,7 @@ public slots: /// edits an overlay updating only the included properties, will return the identified OverlayID in case of /// successful edit, if the input id is for an unknown overlay this function will have no effect - bool editOverlay(unsigned int id, const QScriptValue& properties); + bool editOverlay(unsigned int id, const QVariant& properties); /// deletes a particle void deleteOverlay(unsigned int id); @@ -122,10 +122,10 @@ public slots: unsigned int addPanel(OverlayPanel::Pointer panel); /// creates and adds a panel based on a set of properties - unsigned int addPanel(const QScriptValue& properties); + unsigned int addPanel(const QVariant& properties); /// edit the properties of a panel - void editPanel(unsigned int panelId, const QScriptValue& properties); + void editPanel(unsigned int panelId, const QVariant& properties); /// get a property of a panel OverlayPropertyResult getPanelProperty(unsigned int panelId, const QString& property); diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 175d6fa408..174873a5c7 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -8,8 +8,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - #include #include diff --git a/interface/src/ui/overlays/PanelAttachable.cpp b/interface/src/ui/overlays/PanelAttachable.cpp index da3a37db45..7f1c4e2e50 100644 --- a/interface/src/ui/overlays/PanelAttachable.cpp +++ b/interface/src/ui/overlays/PanelAttachable.cpp @@ -23,53 +23,38 @@ bool PanelAttachable::getParentVisible() const { } } -QScriptValue PanelAttachable::getProperty(QScriptEngine* scriptEngine, const QString &property) { +QVariant PanelAttachable::getProperty(const QString& property) { if (property == "offsetPosition") { - return vec3toScriptValue(scriptEngine, getOffsetPosition()); + return vec3toVariant(getOffsetPosition()); } if (property == "offsetRotation") { - return quatToScriptValue(scriptEngine, getOffsetRotation()); + return quatToVariant(getOffsetRotation()); } if (property == "offsetScale") { - return vec3toScriptValue(scriptEngine, getOffsetScale()); + return vec3toVariant(getOffsetScale()); } - return QScriptValue(); + return QVariant(); } -void PanelAttachable::setProperties(const QScriptValue &properties) { - QScriptValue offsetPosition = properties.property("offsetPosition"); - if (offsetPosition.isValid() && - offsetPosition.property("x").isValid() && - offsetPosition.property("y").isValid() && - offsetPosition.property("z").isValid()) { - glm::vec3 newPosition; - vec3FromScriptValue(offsetPosition, newPosition); - setOffsetPosition(newPosition); - } - - QScriptValue offsetRotation = properties.property("offsetRotation"); - if (offsetRotation.isValid() && - offsetRotation.property("x").isValid() && - offsetRotation.property("y").isValid() && - offsetRotation.property("z").isValid() && - offsetRotation.property("w").isValid()) { - glm::quat newRotation; - quatFromScriptValue(offsetRotation, newRotation); - setOffsetRotation(newRotation); - } - - QScriptValue offsetScale = properties.property("offsetScale"); - if (offsetScale.isValid()) { - if (offsetScale.property("x").isValid() && - offsetScale.property("y").isValid() && - offsetScale.property("z").isValid()) { - glm::vec3 newScale; - vec3FromScriptValue(offsetScale, newScale); - setOffsetScale(newScale); - } else { - setOffsetScale(offsetScale.toVariant().toFloat()); +void PanelAttachable::setProperties(const QVariantMap& properties) { + auto offsetPosition = properties["offsetPosition"]; + bool valid; + if (offsetPosition.isValid()) { + glm::vec3 newPosition = vec3FromVariant(offsetPosition, valid); + if (valid) { + setOffsetPosition(newPosition); } } + + auto offsetRotation = properties["offsetRotation"]; + if (offsetRotation.isValid()) { + setOffsetRotation(quatFromVariant(offsetRotation)); + } + + auto offsetScale = properties["offsetScale"]; + if (offsetScale.isValid()) { + setOffsetScale(vec3FromVariant(offsetScale)); + } } void PanelAttachable::applyTransformTo(Transform& transform, bool force) { diff --git a/interface/src/ui/overlays/PanelAttachable.h b/interface/src/ui/overlays/PanelAttachable.h index f4e982f8e9..270addbfcf 100644 --- a/interface/src/ui/overlays/PanelAttachable.h +++ b/interface/src/ui/overlays/PanelAttachable.h @@ -57,8 +57,8 @@ public: void setOffsetScale(const glm::vec3& scale) { _offset.setScale(scale); } protected: - QScriptValue getProperty(QScriptEngine* scriptEngine, const QString& property); - void setProperties(const QScriptValue& properties); + void setProperties(const QVariantMap& properties); + QVariant getProperty(const QString& property); /// set position, rotation and scale on transform based on offsets, and parent panel offsets /// if force is false, only apply transform if it hasn't been applied in the last .1 seconds diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index b04316b3ee..c580464e16 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -35,57 +35,27 @@ AABox Planar3DOverlay::getBounds() const { return AABox(extents); } -void Planar3DOverlay::setProperties(const QScriptValue& properties) { +void Planar3DOverlay::setProperties(const QVariantMap& properties) { Base3DOverlay::setProperties(properties); - QScriptValue dimensions = properties.property("dimensions"); + auto dimensions = properties["dimensions"]; // if "dimensions" property was not there, check to see if they included aliases: scale if (!dimensions.isValid()) { - dimensions = properties.property("scale"); + dimensions = properties["scale"]; if (!dimensions.isValid()) { - dimensions = properties.property("size"); + dimensions = properties["size"]; } } if (dimensions.isValid()) { - bool validDimensions = false; - glm::vec2 newDimensions; - - QScriptValue x = dimensions.property("x"); - QScriptValue y = dimensions.property("y"); - - if (x.isValid() && y.isValid()) { - newDimensions.x = x.toVariant().toFloat(); - newDimensions.y = y.toVariant().toFloat(); - validDimensions = true; - } else { - QScriptValue width = dimensions.property("width"); - QScriptValue height = dimensions.property("height"); - if (width.isValid() && height.isValid()) { - newDimensions.x = width.toVariant().toFloat(); - newDimensions.y = height.toVariant().toFloat(); - validDimensions = true; - } - } - - // size, scale, dimensions is special, it might just be a single scalar, check that here - if (!validDimensions && dimensions.isNumber()) { - float size = dimensions.toVariant().toFloat(); - newDimensions.x = size; - newDimensions.y = size; - validDimensions = true; - } - - if (validDimensions) { - setDimensions(newDimensions); - } + setDimensions(vec2FromVariant(dimensions)); } } -QScriptValue Planar3DOverlay::getProperty(const QString& property) { +QVariant Planar3DOverlay::getProperty(const QString& property) { if (property == "dimensions" || property == "scale" || property == "size") { - return vec2toScriptValue(_scriptEngine, getDimensions()); + return vec2toVariant(getDimensions()); } return Base3DOverlay::getProperty(property); diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index 0542a8b491..ef391e61e4 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -26,8 +26,8 @@ public: void setDimensions(float value) { _dimensions = glm::vec2(value); } void setDimensions(const glm::vec2& value) { _dimensions = value; } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index 7675489258..27d03b5e09 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -57,7 +57,7 @@ QmlOverlay::~QmlOverlay() { } } -void QmlOverlay::setProperties(const QScriptValue& properties) { +void QmlOverlay::setProperties(const QVariantMap& properties) { Overlay2D::setProperties(properties); auto bounds = _bounds; std::weak_ptr weakQmlElement; @@ -71,7 +71,7 @@ void QmlOverlay::setProperties(const QScriptValue& properties) { _qmlElement->setHeight(bounds.height()); } }); - QMetaObject::invokeMethod(_qmlElement.get(), "updatePropertiesFromScript", Q_ARG(QVariant, properties.toVariant())); + QMetaObject::invokeMethod(_qmlElement.get(), "updatePropertiesFromScript", Q_ARG(QVariant, properties)); } void QmlOverlay::render(RenderArgs* args) { diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h index a75783da1f..736d3884b5 100644 --- a/interface/src/ui/overlays/QmlOverlay.h +++ b/interface/src/ui/overlays/QmlOverlay.h @@ -28,7 +28,7 @@ public: // Cannot fetch properties from QML based overlays due to race conditions bool supportsGetProperty() const override { return false; } - void setProperties(const QScriptValue& properties) override; + void setProperties(const QVariantMap& properties) override; void render(RenderArgs* args) override; private: diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index cee924c44c..5a541fd58a 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -96,7 +96,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { return builder.build(); } -void Rectangle3DOverlay::setProperties(const QScriptValue &properties) { +void Rectangle3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); } diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.h b/interface/src/ui/overlays/Rectangle3DOverlay.h index cde4ad2f53..31ccac45a3 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.h +++ b/interface/src/ui/overlays/Rectangle3DOverlay.h @@ -25,7 +25,7 @@ public: ~Rectangle3DOverlay(); virtual void render(RenderArgs* args); virtual const render::ShapeKey getShapeKey() override; - virtual void setProperties(const QScriptValue& properties); + void setProperties(const QVariantMap& properties) override; virtual Rectangle3DOverlay* createClone() const; private: diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index 60cf6bcd4d..9fa8f6556b 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -121,57 +121,54 @@ const render::ShapeKey Text3DOverlay::getShapeKey() { return builder.build(); } -void Text3DOverlay::setProperties(const QScriptValue& properties) { +void Text3DOverlay::setProperties(const QVariantMap& properties) { Billboard3DOverlay::setProperties(properties); - QScriptValue text = properties.property("text"); + auto text = properties["text"]; if (text.isValid()) { - setText(text.toVariant().toString()); + setText(text.toString()); } - QScriptValue textAlpha = properties.property("textAlpha"); + auto textAlpha = properties["textAlpha"]; if (textAlpha.isValid()) { - setTextAlpha(textAlpha.toVariant().toFloat()); + setTextAlpha(textAlpha.toFloat()); } - QScriptValue backgroundColor = properties.property("backgroundColor"); + bool valid; + auto backgroundColor = properties["backgroundColor"]; if (backgroundColor.isValid()) { - QScriptValue red = backgroundColor.property("red"); - QScriptValue green = backgroundColor.property("green"); - QScriptValue blue = backgroundColor.property("blue"); - if (red.isValid() && green.isValid() && blue.isValid()) { - _backgroundColor.red = red.toVariant().toInt(); - _backgroundColor.green = green.toVariant().toInt(); - _backgroundColor.blue = blue.toVariant().toInt(); + auto color = xColorFromVariant(backgroundColor, valid); + if (valid) { + _backgroundColor = color; } } - if (properties.property("backgroundAlpha").isValid()) { - setAlpha(properties.property("backgroundAlpha").toVariant().toFloat()); + if (properties["backgroundAlpha"].isValid()) { + setAlpha(properties["backgroundAlpha"].toFloat()); } - if (properties.property("lineHeight").isValid()) { - setLineHeight(properties.property("lineHeight").toVariant().toFloat()); + if (properties["lineHeight"].isValid()) { + setLineHeight(properties["lineHeight"].toFloat()); } - if (properties.property("leftMargin").isValid()) { - setLeftMargin(properties.property("leftMargin").toVariant().toFloat()); + if (properties["leftMargin"].isValid()) { + setLeftMargin(properties["leftMargin"].toFloat()); } - if (properties.property("topMargin").isValid()) { - setTopMargin(properties.property("topMargin").toVariant().toFloat()); + if (properties["topMargin"].isValid()) { + setTopMargin(properties["topMargin"].toFloat()); } - if (properties.property("rightMargin").isValid()) { - setRightMargin(properties.property("rightMargin").toVariant().toFloat()); + if (properties["rightMargin"].isValid()) { + setRightMargin(properties["rightMargin"].toFloat()); } - if (properties.property("bottomMargin").isValid()) { - setBottomMargin(properties.property("bottomMargin").toVariant().toFloat()); + if (properties["bottomMargin"].isValid()) { + setBottomMargin(properties["bottomMargin"].toFloat()); } } -QScriptValue Text3DOverlay::getProperty(const QString& property) { +QVariant Text3DOverlay::getProperty(const QString& property) { if (property == "text") { return _text; } @@ -179,7 +176,7 @@ QScriptValue Text3DOverlay::getProperty(const QString& property) { return _textAlpha; } if (property == "backgroundColor") { - return xColorToScriptValue(_scriptEngine, _backgroundColor); + return xColorToVariant(_backgroundColor); } if (property == "backgroundAlpha") { return Billboard3DOverlay::getProperty("alpha"); diff --git a/interface/src/ui/overlays/Text3DOverlay.h b/interface/src/ui/overlays/Text3DOverlay.h index e9635947b6..181e9d526c 100644 --- a/interface/src/ui/overlays/Text3DOverlay.h +++ b/interface/src/ui/overlays/Text3DOverlay.h @@ -53,8 +53,8 @@ public: void setRightMargin(float margin) { _rightMargin = margin; } void setBottomMargin(float margin) { _bottomMargin = margin; } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; QSizeF textSize(const QString& test) const; // Meters diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 687f1f6286..c8078d35c6 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -26,67 +26,27 @@ AABox Volume3DOverlay::getBounds() const { return AABox(extents); } -void Volume3DOverlay::setProperties(const QScriptValue& properties) { +void Volume3DOverlay::setProperties(const QVariantMap& properties) { Base3DOverlay::setProperties(properties); - QScriptValue dimensions = properties.property("dimensions"); + auto dimensions = properties["dimensions"]; // if "dimensions" property was not there, check to see if they included aliases: scale if (!dimensions.isValid()) { - dimensions = properties.property("scale"); + dimensions = properties["scale"]; if (!dimensions.isValid()) { - dimensions = properties.property("size"); + dimensions = properties["size"]; } } if (dimensions.isValid()) { - bool validDimensions = false; - glm::vec3 newDimensions; - - QScriptValue x = dimensions.property("x"); - QScriptValue y = dimensions.property("y"); - QScriptValue z = dimensions.property("z"); - - - if (x.isValid() && x.isNumber() && - y.isValid() && y.isNumber() && - z.isValid() && z.isNumber()) { - newDimensions.x = x.toNumber(); - newDimensions.y = y.toNumber(); - newDimensions.z = z.toNumber(); - validDimensions = true; - } else { - QScriptValue width = dimensions.property("width"); - QScriptValue height = dimensions.property("height"); - QScriptValue depth = dimensions.property("depth"); - if (width.isValid() && width.isNumber() && - height.isValid() && height.isNumber() && - depth.isValid() && depth.isNumber()) { - newDimensions.x = width.toNumber(); - newDimensions.y = height.toNumber(); - newDimensions.z = depth.toNumber(); - validDimensions = true; - } - } - - // size, scale, dimensions is special, it might just be a single scalar, check that here - if (!validDimensions && dimensions.isNumber()) { - float size = dimensions.toNumber(); - newDimensions.x = size; - newDimensions.y = size; - newDimensions.z = size; - validDimensions = true; - } - - if (validDimensions) { - setDimensions(newDimensions); - } + setDimensions(vec3FromVariant(dimensions)); } } -QScriptValue Volume3DOverlay::getProperty(const QString& property) { +QVariant Volume3DOverlay::getProperty(const QString& property) { if (property == "dimensions" || property == "scale" || property == "size") { - return vec3toScriptValue(_scriptEngine, getDimensions()); + return vec3toVariant(getDimensions()); } return Base3DOverlay::getProperty(property); diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 5756b6549a..42b3d439d6 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -26,8 +26,8 @@ public: void setDimensions(float value) { _localBoundingBox.setBox(glm::vec3(-value / 2.0f), value); } void setDimensions(const glm::vec3& value) { _localBoundingBox.setBox(-value / 2.0f, value); } - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 7ceff81dd2..f5baecd96a 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -12,7 +12,6 @@ #include "Web3DOverlay.h" -#include #include #include @@ -113,30 +112,34 @@ const render::ShapeKey Web3DOverlay::getShapeKey() { return builder.build(); } -void Web3DOverlay::setProperties(const QScriptValue &properties) { +void Web3DOverlay::setProperties(const QVariantMap& properties) { Billboard3DOverlay::setProperties(properties); - QScriptValue urlValue = properties.property("url"); + auto urlValue = properties["url"]; if (urlValue.isValid()) { - QString newURL = urlValue.toVariant().toString(); + QString newURL = urlValue.toString(); if (newURL != _url) { setURL(newURL); } } - QScriptValue resolution = properties.property("resolution"); + auto resolution = properties["resolution"]; if (resolution.isValid()) { - vec2FromScriptValue(resolution, _resolution); + bool valid; + auto res = vec2FromVariant(resolution, valid); + if (valid) { + _resolution = res; + } } - QScriptValue dpi = properties.property("dpi"); + auto dpi = properties["dpi"]; if (dpi.isValid()) { - _dpi = dpi.toVariant().toFloat(); + _dpi = dpi.toFloat(); } } -QScriptValue Web3DOverlay::getProperty(const QString& property) { +QVariant Web3DOverlay::getProperty(const QString& property) { if (property == "url") { return _url; } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index b1715dff46..062cff61d6 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -32,8 +32,8 @@ public: // setters void setURL(const QString& url); - virtual void setProperties(const QScriptValue& properties); - virtual QScriptValue getProperty(const QString& property); + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal); diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 48ad9b852d..6a29ad61ac 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -627,6 +627,9 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + stConstraint->setTwistLimits(0.0f, 0.0f); // max == min, disables twist limits + + /* KEEP THIS CODE for future experimentation -- twist limits for hands const float MAX_HAND_TWIST = 3.0f * PI / 5.0f; const float MIN_HAND_TWIST = -PI / 2.0f; if (isLeft) { @@ -634,8 +637,9 @@ void AnimInverseKinematics::initConstraints() { } else { stConstraint->setTwistLimits(MIN_HAND_TWIST, MAX_HAND_TWIST); } + */ - /* KEEP THIS CODE for future experimentation + /* KEEP THIS CODE for future experimentation -- non-symmetrical swing limits for wrist * a more complicated wrist with asymmetric cone // these directions are approximate swing limits in parent-frame // NOTE: they don't need to be normalized @@ -670,7 +674,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setTwistLimits(-MAX_SHOULDER_TWIST, MAX_SHOULDER_TWIST); std::vector minDots; - const float MAX_SHOULDER_SWING = PI / 6.0f; + const float MAX_SHOULDER_SWING = PI / 20.0f; minDots.push_back(cosf(MAX_SHOULDER_SWING)); stConstraint->setSwingLimits(minDots); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3952dc5b40..02968e3665 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -173,8 +173,6 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff _animSkeleton = std::make_shared(geometry); - computeEyesInRootFrame(_animSkeleton->getRelativeDefaultPoses()); - _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); @@ -201,8 +199,6 @@ void Rig::reset(const FBXGeometry& geometry) { _geometryOffset = AnimPose(geometry.offset); _animSkeleton = std::make_shared(geometry); - computeEyesInRootFrame(_animSkeleton->getRelativeDefaultPoses()); - _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); @@ -237,10 +233,20 @@ int Rig::getJointStateCount() const { return (int)_internalPoseSet._relativePoses.size(); } +static const uint32_t MAX_JOINT_NAME_WARNING_COUNT = 100; + int Rig::indexOfJoint(const QString& jointName) const { if (_animSkeleton) { - return _animSkeleton->nameToJointIndex(jointName); + int result = _animSkeleton->nameToJointIndex(jointName); + + // This is a content error, so we should issue a warning. + if (result < 0 && _jointNameWarningCount < MAX_JOINT_NAME_WARNING_COUNT) { + qCWarning(animation) << "Rig: Missing joint" << jointName << "in avatar model"; + _jointNameWarningCount++; + } + return result; } else { + // This is normal and can happen when the avatar model has not been dowloaded/loaded yet. return -1; } } @@ -444,26 +450,6 @@ void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, *alphaOut = alpha; } -void Rig::computeEyesInRootFrame(const AnimPoseVec& poses) { - // TODO: use cached eye/hips indices for these calculations - int numPoses = (int)poses.size(); - int hipsIndex = _animSkeleton->nameToJointIndex(QString("Hips")); - int headIndex = _animSkeleton->nameToJointIndex(QString("Head")); - if (hipsIndex > 0 && headIndex > 0) { - int rightEyeIndex = _animSkeleton->nameToJointIndex(QString("RightEye")); - int leftEyeIndex = _animSkeleton->nameToJointIndex(QString("LeftEye")); - if (numPoses > rightEyeIndex && numPoses > leftEyeIndex && rightEyeIndex > 0 && leftEyeIndex > 0) { - glm::vec3 rightEye = _animSkeleton->getAbsolutePose(rightEyeIndex, poses).trans; - glm::vec3 leftEye = _animSkeleton->getAbsolutePose(leftEyeIndex, poses).trans; - glm::vec3 hips = _animSkeleton->getAbsolutePose(hipsIndex, poses).trans; - _eyesInRootFrame = 0.5f * (rightEye + leftEye) - hips; - } else { - glm::vec3 hips = _animSkeleton->getAbsolutePose(hipsIndex, poses).trans; - _eyesInRootFrame = 0.5f * (DEFAULT_RIGHT_EYE_POS + DEFAULT_LEFT_EYE_POS) - hips; - } - } -} - void Rig::setEnableInverseKinematics(bool enable) { _enableInverseKinematics = enable; } @@ -893,8 +879,6 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { for (auto& trigger : triggersOut) { _animVars.setTrigger(trigger); } - - computeEyesInRootFrame(_internalPoseSet._relativePoses); } applyOverridePoses(); @@ -1067,14 +1051,21 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm glm::mat4 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation); glm::mat4 worldToRig = glm::inverse(rigToWorld); glm::vec3 zAxis = glm::normalize(_internalPoseSet._absolutePoses[index].trans - transformPoint(worldToRig, lookAtSpot)); - glm::quat q = rotationBetween(IDENTITY_FRONT, zAxis); + + glm::quat desiredQuat = rotationBetween(IDENTITY_FRONT, zAxis); + glm::quat headQuat; + int headIndex = indexOfJoint("Head"); + if (headIndex >= 0) { + headQuat = _internalPoseSet._absolutePoses[headIndex].rot; + } + glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); // limit rotation const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - q = glm::angleAxis(glm::clamp(glm::angle(q), -MAX_ANGLE, MAX_ANGLE), glm::axis(q)); + deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); // directly set absolutePose rotation - _internalPoseSet._absolutePoses[index].rot = q; + _internalPoseSet._absolutePoses[index].rot = deltaQuat * headQuat; } } @@ -1086,7 +1077,11 @@ void Rig::updateFromHandParameters(const HandParameters& params, float dt) { const float MIN_LENGTH = 1.0e-4f; // project the hips onto the xz plane. - auto hipsTrans = _internalPoseSet._absolutePoses[_animSkeleton->nameToJointIndex("Hips")].trans; + int hipsIndex = indexOfJoint("Hips"); + glm::vec3 hipsTrans; + if (hipsIndex >= 0) { + hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans; + } const glm::vec2 bodyCircleCenter(hipsTrans.x, hipsTrans.z); if (params.isLeftEnabled) { @@ -1271,7 +1266,11 @@ void Rig::computeAvatarBoundingCapsule( AnimPose geometryToRig = _modelOffset * _geometryOffset; - AnimPose hips = geometryToRig * _animSkeleton->getAbsoluteBindPose(_animSkeleton->nameToJointIndex("Hips")); + AnimPose hips(glm::vec3(1), glm::quat(), glm::vec3()); + int hipsIndex = indexOfJoint("Hips"); + if (hipsIndex >= 0) { + hips = geometryToRig * _animSkeleton->getAbsoluteBindPose(hipsIndex); + } AnimVariantMap animVars; glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X); animVars.set("leftHandPosition", hips.trans); @@ -1281,8 +1280,8 @@ void Rig::computeAvatarBoundingCapsule( animVars.set("rightHandRotation", handRotation); animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); - int rightFootIndex = _animSkeleton->nameToJointIndex("RightFoot"); - int leftFootIndex = _animSkeleton->nameToJointIndex("LeftFoot"); + int rightFootIndex = indexOfJoint("RightFoot"); + int leftFootIndex = indexOfJoint("LeftFoot"); if (rightFootIndex != -1 && leftFootIndex != -1) { glm::vec3 foot = Vectors::ZERO; glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X); @@ -1314,7 +1313,7 @@ void Rig::computeAvatarBoundingCapsule( // HACK to reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. - int index = _animSkeleton->nameToJointIndex(QString("Head")); + int index = indexOfJoint("Head"); while (index != -1) { const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; AnimPose pose = finalPoses[index]; @@ -1337,3 +1336,5 @@ void Rig::computeAvatarBoundingCapsule( glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum))); localOffsetOut = rigCenter - (geometryToRig * rootPosition); } + + diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 9c5b014d55..3d5d44b844 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -231,8 +231,6 @@ public: void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; - void computeEyesInRootFrame(const AnimPoseVec& poses); - AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) @@ -305,6 +303,8 @@ public: bool _lastEnableInverseKinematics { true }; bool _enableInverseKinematics { true }; + mutable uint32_t _jointNameWarningCount { 0 }; + private: QMap _stateHandlers; int _nextStateHandlerId { 0 }; diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index 7386fb2bcd..3bf661612e 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -182,49 +182,52 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const { glm::vec3 twistedX = twistRotation * xAxis; twistAngle *= copysignf(1.0f, glm::dot(glm::cross(xAxis, twistedX), yAxis)); - // adjust measured twistAngle according to clamping history - switch (_lastTwistBoundary) { - case LAST_CLAMP_LOW_BOUNDARY: - // clamp to min - if (twistAngle > _maxTwist) { - twistAngle -= TWO_PI; - } - break; - case LAST_CLAMP_HIGH_BOUNDARY: - // clamp to max - if (twistAngle < _minTwist) { - twistAngle += TWO_PI; - } - break; - default: // LAST_CLAMP_NO_BOUNDARY - // clamp to nearest boundary - float midBoundary = 0.5f * (_maxTwist + _minTwist + TWO_PI); - if (twistAngle > midBoundary) { - // lower boundary is closer --> phase down one cycle - twistAngle -= TWO_PI; - } else if (twistAngle < midBoundary - TWO_PI) { - // higher boundary is closer --> phase up one cycle - twistAngle += TWO_PI; - } - break; - } + bool somethingClamped = false; + if (_minTwist != _maxTwist) { + // adjust measured twistAngle according to clamping history + switch (_lastTwistBoundary) { + case LAST_CLAMP_LOW_BOUNDARY: + // clamp to min + if (twistAngle > _maxTwist) { + twistAngle -= TWO_PI; + } + break; + case LAST_CLAMP_HIGH_BOUNDARY: + // clamp to max + if (twistAngle < _minTwist) { + twistAngle += TWO_PI; + } + break; + default: // LAST_CLAMP_NO_BOUNDARY + // clamp to nearest boundary + float midBoundary = 0.5f * (_maxTwist + _minTwist + TWO_PI); + if (twistAngle > midBoundary) { + // lower boundary is closer --> phase down one cycle + twistAngle -= TWO_PI; + } else if (twistAngle < midBoundary - TWO_PI) { + // higher boundary is closer --> phase up one cycle + twistAngle += TWO_PI; + } + break; + } - // clamp twistAngle - float clampedTwistAngle = glm::clamp(twistAngle, _minTwist, _maxTwist); - bool twistWasClamped = (twistAngle != clampedTwistAngle); + // clamp twistAngle + float clampedTwistAngle = glm::clamp(twistAngle, _minTwist, _maxTwist); + somethingClamped = (twistAngle != clampedTwistAngle); - // remember twist's clamp boundary history - if (twistWasClamped) { - _lastTwistBoundary = (twistAngle > clampedTwistAngle) ? LAST_CLAMP_HIGH_BOUNDARY : LAST_CLAMP_LOW_BOUNDARY; - } else { - _lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY; + // remember twist's clamp boundary history + if (somethingClamped) { + _lastTwistBoundary = (twistAngle > clampedTwistAngle) ? LAST_CLAMP_HIGH_BOUNDARY : LAST_CLAMP_LOW_BOUNDARY; + twistAngle = clampedTwistAngle; + } else { + _lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY; + } } // clamp the swing // The swingAxis is always perpendicular to the reference axis (yAxis in the constraint's frame). glm::vec3 swungY = swingRotation * yAxis; glm::vec3 swingAxis = glm::cross(yAxis, swungY); - bool swingWasClamped = false; float axisLength = glm::length(swingAxis); if (axisLength > EPSILON) { // The limit of swing is a function of "theta" which can be computed from the swingAxis @@ -236,13 +239,13 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const { // use it to supply a new rotation. swingAxis /= axisLength; swingRotation = glm::angleAxis(acosf(minDot), swingAxis); - swingWasClamped = true; + somethingClamped = true; } } - if (swingWasClamped || twistWasClamped) { + if (somethingClamped) { // update the rotation - twistRotation = glm::angleAxis(clampedTwistAngle, yAxis); + twistRotation = glm::angleAxis(twistAngle, yAxis); rotation = swingRotation * twistRotation * _referenceRotation; return true; } diff --git a/libraries/audio/src/AudioLimiter.cpp b/libraries/audio/src/AudioLimiter.cpp new file mode 100644 index 0000000000..d9257b7df5 --- /dev/null +++ b/libraries/audio/src/AudioLimiter.cpp @@ -0,0 +1,717 @@ +// +// AudioLimiter.cpp +// libraries/audio/src +// +// Created by Ken Cooke on 2/11/15. +// Copyright 2016 High Fidelity, Inc. +// + +#include +#include + +#include "AudioLimiter.h" + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef _MSC_VER + +#include +#define MUL64(a,b) __emul((a), (b)) +#define MULHI(a,b) ((int)(MUL64(a, b) >> 32)) +#define MULQ31(a,b) ((int)(MUL64(a, b) >> 31)) + +#else + +#define MUL64(a,b) ((long long)(a) * (b)) +#define MULHI(a,b) ((int)(MUL64(a, b) >> 32)) +#define MULQ31(a,b) ((int)(MUL64(a, b) >> 31)) + +#endif // _MSC_VER + +// +// on x86 architecture, assume that SSE2 is present +// +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include +// convert float to int using round-to-nearest +static inline int32_t floatToInt(float x) { + return _mm_cvt_ss2si(_mm_load_ss(&x)); +} + +#else + +// convert float to int using round-to-nearest +static inline int32_t floatToInt(float x) { + x += (x < 0.0f ? -0.5f : 0.5f); // round + return (int32_t)x; +} + +#endif // _M_IX86 + +static const double FIXQ31 = 2147483648.0; // convert float to Q31 +static const double DB_TO_LOG2 = 0.16609640474436813; // convert dB to log2 + +// convert dB to amplitude +static double dBToGain(double dB) { + return pow(10.0, dB / 20.0); +} + +// convert milliseconds to first-order time constant +static int32_t msToTc(double ms, double sampleRate) { + double tc = exp(-1000.0 / (ms * sampleRate)); + return (int32_t)(FIXQ31 * tc); // Q31 +} + +// log2 domain values are Q26 +static const int LOG2_INTBITS = 5; +static const int LOG2_FRACBITS = 31 - LOG2_INTBITS; + +// log2 domain headroom bits above 0dB +static const int LOG2_HEADROOM = 15; + +// log2 domain offsets so error < 0 +static const int32_t LOG2_BIAS = 347; +static const int32_t EXP2_BIAS = 64; + +// +// P(x) = log2(1+x) for x=[0,1] +// scaled by 1, 0.5, 0.25 +// +// |error| < 347 ulp, smooth +// +static const int LOG2_TABBITS = 4; +static const int32_t log2Table[1 << LOG2_TABBITS][3] = { + { -0x56dfe26d, 0x5c46daff, 0x00000000 }, + { -0x4d397571, 0x5bae58e7, 0x00025a75 }, + { -0x4518f84b, 0x5aabcac4, 0x000a62db }, + { -0x3e3075ec, 0x596168c0, 0x0019d0e6 }, + { -0x384486e9, 0x57e769c7, 0x00316109 }, + { -0x332742ba, 0x564f1461, 0x00513776 }, + { -0x2eb4bad4, 0x54a4cdfe, 0x00791de2 }, + { -0x2ad07c6c, 0x52f18320, 0x00a8aa46 }, + { -0x2763c4d6, 0x513ba123, 0x00df574c }, + { -0x245c319b, 0x4f87c5c4, 0x011c9399 }, + { -0x21aac79f, 0x4dd93bef, 0x015fcb52 }, + { -0x1f433872, 0x4c325584, 0x01a86ddc }, + { -0x1d1b54b4, 0x4a94ac6e, 0x01f5f13e }, + { -0x1b2a9f81, 0x4901524f, 0x0247d3f2 }, + { -0x1969fa57, 0x4778f3a7, 0x029d9dbf }, + { -0x17d36370, 0x45fbf1e8, 0x02f6dfe8 }, +}; + +// +// P(x) = exp2(x) for x=[0,1] +// scaled by 2, 1, 0.5 +// Uses exp2(-x) = exp2(1-x)/2 +// +// |error| < 1387 ulp, smooth +// +static const int EXP2_TABBITS = 4; +static const int32_t exp2Table[1 << EXP2_TABBITS][3] = { + { 0x3ed838c8, 0x58b574b7, 0x40000000 }, + { 0x41a0821c, 0x5888db8f, 0x4000b2b7 }, + { 0x4488548d, 0x582bcbc6, 0x40039be1 }, + { 0x4791158a, 0x579a1128, 0x400a71ae }, + { 0x4abc3a53, 0x56cf3089, 0x4017212e }, + { 0x4e0b48af, 0x55c66396, 0x402bd31b }, + { 0x517fd7a7, 0x547a946d, 0x404af0ec }, + { 0x551b9049, 0x52e658f9, 0x40772a57 }, + { 0x58e02e75, 0x5103ee08, 0x40b37b31 }, + { 0x5ccf81b1, 0x4ecd321f, 0x410331b5 }, + { 0x60eb6e09, 0x4c3ba007, 0x4169f548 }, + { 0x6535ecf9, 0x49484909, 0x41ebcdaf }, + { 0x69b10e5b, 0x45ebcede, 0x428d2acd }, + { 0x6e5ef96c, 0x421e5d48, 0x4352ece7 }, + { 0x7341edcb, 0x3dd7a354, 0x44426d7b }, + { 0x785c4499, 0x390ecc3a, 0x456188bd }, +}; + +static const int IEEE754_FABS_MASK = 0x7fffffff; +static const int IEEE754_MANT_BITS = 23; +static const int IEEE754_EXPN_BIAS = 127; + +// +// Peak detection and -log2(x) for float input (mono) +// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff +// x > 2^LOG2_HEADROOM undefined +// +static inline int32_t peaklog2(float* input) { + + // float as integer bits + int32_t u = *(int32_t*)input; + + // absolute value + int32_t peak = u & IEEE754_FABS_MASK; + + // split into e and x - 1.0 + int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; + + // saturate + if (e > 31) { + return 0x7fffffff; + } + + int k = x >> (31 - LOG2_TABBITS); + + // polynomial for log2(1+x) over x=[0,1] + int32_t c0 = log2Table[k][0]; + int32_t c1 = log2Table[k][1]; + int32_t c2 = log2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q26 + return (e << LOG2_FRACBITS) - (c2 >> 3); +} + +// +// Peak detection and -log2(x) for float input (stereo) +// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff +// x > 2^LOG2_HEADROOM undefined +// +static inline int32_t peaklog2(float* input0, float* input1) { + + // float as integer bits + int32_t u0 = *(int32_t*)input0; + int32_t u1 = *(int32_t*)input1; + + // max absolute value + u0 &= IEEE754_FABS_MASK; + u1 &= IEEE754_FABS_MASK; + int32_t peak = MAX(u0, u1); + + // split into e and x - 1.0 + int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff; + + // saturate + if (e > 31) { + return 0x7fffffff; + } + + int k = x >> (31 - LOG2_TABBITS); + + // polynomial for log2(1+x) over x=[0,1] + int32_t c0 = log2Table[k][0]; + int32_t c1 = log2Table[k][1]; + int32_t c2 = log2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q26 + return (e << LOG2_FRACBITS) - (c2 >> 3); +} + +// +// Compute exp2(-x) for x=[0,32] in Q26, result in Q31 +// x < 0 undefined +// +static inline int32_t fixexp2(int32_t x) { + + // split into e and 1.0 - x + int32_t e = x >> LOG2_FRACBITS; + x = ~(x << LOG2_INTBITS) & 0x7fffffff; + + int k = x >> (31 - EXP2_TABBITS); + + // polynomial for exp2(x) + int32_t c0 = exp2Table[k][0]; + int32_t c1 = exp2Table[k][1]; + int32_t c2 = exp2Table[k][2]; + + c1 += MULHI(c0, x); + c2 += MULHI(c1, x); + + // reconstruct result in Q31 + return c2 >> e; +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline float dither() { + static uint32_t rz = 0; + rz = rz * 69069 + 1; + int32_t r0 = rz & 0xffff; + int32_t r1 = rz >> 16; + return (int32_t)(r0 - r1) * (1/65536.0f); +} + +// +// Peak-hold lowpass filter +// +// Bandlimits the gain control signal to greatly reduce the modulation distortion, +// while still reaching the peak attenuation after exactly N-1 samples of delay. +// N completely determines the limiter attack time. +// +template +class PeakFilterT { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + static_assert((CIC1 - 1) + (CIC2 - 1) == (N - 1), "Total CIC delay must be N-1"); + + int32_t _buffer[2*N] = {}; // shared FIFO + int _index = 0; + + int32_t _acc1 = 0; // CIC1 integrator + int32_t _acc2 = 0; // CIC2 integrator + +public: + PeakFilterT() { + + // fill history + for (int n = 0; n < N-1; n++) { + process(0x7fffffff); + } + } + + int32_t process(int32_t x) { + + const int MASK = 2*N - 1; // buffer wrap + int i = _index; + + // Fast peak-hold using a running-min filter. Finds the peak (min) value + // in the sliding window of N-1 samples, using only log2(N) comparisons. + // Hold time of N-1 samples exactly cancels the step response of FIR filter. + + for (int n = 1; n < N; n <<= 1) { + + _buffer[i] = x; + i = (i + n) & MASK; + x = MIN(x, _buffer[i]); + } + + // Fast FIR attack/lowpass filter using a 2-stage CIC filter. + // The step response reaches final value after N-1 samples. + + const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 + x = MULHI(x, CICGAIN); + + _buffer[i] = _acc1; + _acc1 += x; // integrator + i = (i + CIC1 - 1) & MASK; + x = _acc1 - _buffer[i]; // comb + + _buffer[i] = _acc2; + _acc2 += x; // integrator + i = (i + CIC2 - 1) & MASK; + x = _acc2 - _buffer[i]; // comb + + _index = (i + 1) & MASK; // skip unused tap + return x; + } +}; + +// +// Specializations that define the optimum lowpass filter for each length. +// +template class PeakFilter; + +template<> class PeakFilter< 16> : public PeakFilterT< 16, 7, 10> {}; +template<> class PeakFilter< 32> : public PeakFilterT< 32, 14, 19> {}; +template<> class PeakFilter< 64> : public PeakFilterT< 64, 27, 38> {}; +template<> class PeakFilter<128> : public PeakFilterT<128, 53, 76> {}; +template<> class PeakFilter<256> : public PeakFilterT<256, 106, 151> {}; + +// +// N-1 sample delay (mono) +// +template +class MonoDelay { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + + float _buffer[N] = {}; + int _index = 0; + +public: + void process(float& x) { + + const int MASK = N - 1; // buffer wrap + int i = _index; + + _buffer[i] = x; + + i = (i + (N - 1)) & MASK; + + x = _buffer[i]; + + _index = i; + } +}; + +// +// N-1 sample delay (stereo) +// +template +class StereoDelay { + + static_assert((N & (N - 1)) == 0, "N must be a power of 2"); + + float _buffer[2*N] = {}; + int _index = 0; + +public: + void process(float& x0, float& x1) { + + const int MASK = 2*N - 1; // buffer wrap + int i = _index; + + _buffer[i+0] = x0; + _buffer[i+1] = x1; + + i = (i + 2*(N - 1)) & MASK; + + x0 = _buffer[i+0]; + x1 = _buffer[i+1]; + + _index = i; + } +}; + +// +// Limiter (common) +// +class LimiterImpl { +protected: + + static const int NARC = 64; + int32_t _holdTable[NARC]; + int32_t _releaseTable[NARC]; + + int32_t _rmsAttack = 0x7fffffff; + int32_t _rmsRelease = 0x7fffffff; + int32_t _arcRelease = 0x7fffffff; + + int32_t _threshold = 0; + int32_t _attn = 0; + int32_t _rms = 0; + int32_t _arc = 0; + + int _sampleRate; + float _outGain = 0.0f; + +public: + LimiterImpl(int sampleRate); + virtual ~LimiterImpl() {} + + void setThreshold(float threshold); + void setRelease(float release); + + int32_t envelope(int32_t attn); + + virtual void process(float* input, int16_t* output, int numFrames) = 0; +}; + +LimiterImpl::LimiterImpl(int sampleRate) { + + sampleRate = MAX(sampleRate, 8000); + sampleRate = MIN(sampleRate, 96000); + _sampleRate = sampleRate; + + // defaults + setThreshold(0.0); + setRelease(250.0); +} + +// +// Set the limiter threshold (dB) +// Brickwall limiting will begin when the signal exceeds the threshold. +// Makeup gain is applied, to reach but never exceed the output ceiling. +// +void LimiterImpl::setThreshold(float threshold) { + + const double OUT_CEILING = -0.3; + const double Q31_TO_Q15 = 32768 / 2147483648.0; + + // limiter threshold = -48dB to 0dB + threshold = MAX(threshold, -48.0f); + threshold = MIN(threshold, 0.0f); + + // limiter threshold in log2 domain + _threshold = (int32_t)(-(double)threshold * DB_TO_LOG2 * (1 << LOG2_FRACBITS)); + _threshold += LOG2_BIAS + EXP2_BIAS; + _threshold += LOG2_HEADROOM << LOG2_FRACBITS; + + // makeup gain and conversion to 16-bit + _outGain = (float)(dBToGain(OUT_CEILING - (double)threshold) * Q31_TO_Q15); +} + +// +// Set the limiter release time (milliseconds) +// This is a base value that scales the adaptive hold and release algorithms. +// +void LimiterImpl::setRelease(float release) { + + const double MAXHOLD = 0.100; // max hold = 100ms + const double MINREL = 0.025; // min release = 0.025 * release + const int NHOLD = 16; // adaptive hold to adaptive release transition + + // limiter release = 50 to 5000ms + release = MAX(release, 50.0f); + release = MIN(release, 5000.0f); + + int32_t maxRelease = msToTc((double)release, _sampleRate); + + _rmsAttack = msToTc(0.1 * (double)release, _sampleRate); + _rmsRelease = maxRelease; + + // Compute ARC tables, working from low peak/rms to high peak/rms. + // + // At low peak/rms, release = max and hold is progressive to max + // At high peak/rms, hold = 0 and release is progressive to min + + double x = MAXHOLD * _sampleRate; + double xstep = x / NHOLD; // 1.0 to 1.0/NHOLD + + int i = 0; + for (; i < NHOLD; i++) { + + // max release + _releaseTable[i] = maxRelease; + + // progressive hold + _holdTable[i] = (int32_t)((maxRelease - 0x7fffffff) / x); + _holdTable[i] = MIN(_holdTable[i], -1); // prevent 0 on long releases + + x -= xstep; + x = MAX(x, 1.0); + } + + x = release; + xstep = x * (1.0-MINREL) / (NARC-NHOLD-1); // 1.0 to MINREL + + for (; i < NARC; i++) { + + // progressive release + _releaseTable[i] = msToTc(x, _sampleRate); + + // min hold + _holdTable[i] = (_releaseTable[i] - 0x7fffffff); // 1 sample + + x -= xstep; + } +} + +// +// Limiter envelope processing +// zero attack, adaptive hold and release +// +int32_t LimiterImpl::envelope(int32_t attn) { + + // table of (1/attn) for 1dB to 6dB, rounded to prevent overflow + static const int16_t invTable[64] = { + 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, + 0x6000, 0x6000, 0x5d17, 0x5555, 0x4ec4, 0x4924, 0x4444, 0x4000, + 0x3c3c, 0x38e3, 0x35e5, 0x3333, 0x30c3, 0x2e8b, 0x2c85, 0x2aaa, + 0x28f5, 0x2762, 0x25ed, 0x2492, 0x234f, 0x2222, 0x2108, 0x2000, + 0x1f07, 0x1e1e, 0x1d41, 0x1c71, 0x1bac, 0x1af2, 0x1a41, 0x1999, + 0x18f9, 0x1861, 0x17d0, 0x1745, 0x16c1, 0x1642, 0x15c9, 0x1555, + 0x14e5, 0x147a, 0x1414, 0x13b1, 0x1352, 0x12f6, 0x129e, 0x1249, + 0x11f7, 0x11a7, 0x115b, 0x1111, 0x10c9, 0x1084, 0x1041, 0x1000, + }; + + if (attn < _attn) { + + // RELEASE + // update release before use, to implement hold = 0 + + _arcRelease += _holdTable[_arc]; // update progressive hold + _arcRelease = MAX(_arcRelease, _releaseTable[_arc]); // saturate at final value + + attn += MULQ31((_attn - attn), _arcRelease); // apply release + + } else { + + // ATTACK + // update ARC with normalized peak/rms + // + // arc = (attn-rms)*6/1 for attn < 1dB + // arc = (attn-rms)*6/attn for attn = 1dB to 6dB + // arc = (attn-rms)*6/6 for attn > 6dB + + int bits = MIN(attn >> 20, 0x3f); // saturate 1/attn at 6dB + _arc = MAX(attn - _rms, 0); // peak/rms = (attn-rms) + _arc = MULHI(_arc, invTable[bits]); // normalized peak/rms = (attn-rms)/attn + _arc = MIN(_arc, NARC - 1); // saturate at 6dB + + _arcRelease = 0x7fffffff; // reset release + } + _attn = attn; + + // Update the RMS estimate after release is applied. + // The feedback loop with adaptive hold will damp any sustained modulation distortion. + + int32_t tc = (attn > _rms) ? _rmsAttack : _rmsRelease; + _rms = attn + MULQ31((_rms - attn), tc); + + return attn; +} + +// +// Limiter (mono) +// +template +class LimiterMono : public LimiterImpl { + + PeakFilter _filter; + MonoDelay _delay; + +public: + LimiterMono(int sampleRate) : LimiterImpl(sampleRate) {} + + void process(float* input, int16_t* output, int numFrames); +}; + +template +void LimiterMono::process(float* input, int16_t* output, int numFrames) +{ + for (int n = 0; n < numFrames; n++) { + + // peak detect and convert to log2 domain + int32_t peak = peaklog2(&input[n]); + + // compute limiter attenuation + int32_t attn = MAX(_threshold - peak, 0); + + // apply envelope + attn = envelope(attn); + + // convert from log2 domain + attn = fixexp2(attn); + + // lowpass filter + attn = _filter.process(attn); + float gain = attn * _outGain; + + // delay audio + float x = input[n]; + _delay.process(x); + + // apply gain + x *= gain; + + // apply dither + x += dither(); + + // store 16-bit output + output[n] = (int16_t)floatToInt(x); + } +} + +// +// Limiter (stereo) +// +template +class LimiterStereo : public LimiterImpl { + + PeakFilter _filter; + StereoDelay _delay; + +public: + LimiterStereo(int sampleRate) : LimiterImpl(sampleRate) {} + + // interleaved stereo input/output + void process(float* input, int16_t* output, int numFrames); +}; + +template +void LimiterStereo::process(float* input, int16_t* output, int numFrames) +{ + for (int n = 0; n < numFrames; n++) { + + // peak detect and convert to log2 domain + int32_t peak = peaklog2(&input[2*n+0], &input[2*n+1]); + + // compute limiter attenuation + int32_t attn = MAX(_threshold - peak, 0); + + // apply envelope + attn = envelope(attn); + + // convert from log2 domain + attn = fixexp2(attn); + + // lowpass filter + attn = _filter.process(attn); + float gain = attn * _outGain; + + // delay audio + float x0 = input[2*n+0]; + float x1 = input[2*n+1]; + _delay.process(x0, x1); + + // apply gain + x0 *= gain; + x1 *= gain; + + // apply dither + float d = dither(); + x0 += d; + x1 += d; + + // store 16-bit output + output[2*n+0] = (int16_t)floatToInt(x0); + output[2*n+1] = (int16_t)floatToInt(x1); + } +} + +// +// Public API +// + +AudioLimiter::AudioLimiter(int sampleRate, int numChannels) { + + if (numChannels == 1) { + + // ~1.5ms lookahead for all rates + if (sampleRate < 16000) { + _impl = new LimiterMono<16>(sampleRate); + } else if (sampleRate < 32000) { + _impl = new LimiterMono<32>(sampleRate); + } else if (sampleRate < 64000) { + _impl = new LimiterMono<64>(sampleRate); + } else { + _impl = new LimiterMono<128>(sampleRate); + } + + } else if (numChannels == 2) { + + // ~1.5ms lookahead for all rates + if (sampleRate < 16000) { + _impl = new LimiterStereo<16>(sampleRate); + } else if (sampleRate < 32000) { + _impl = new LimiterStereo<32>(sampleRate); + } else if (sampleRate < 64000) { + _impl = new LimiterStereo<64>(sampleRate); + } else { + _impl = new LimiterStereo<128>(sampleRate); + } + + } else { + assert(0); // unsupported + } +} + +AudioLimiter::~AudioLimiter() { + delete _impl; +} + +void AudioLimiter::render(float* input, int16_t* output, int numFrames) { + _impl->process(input, output, numFrames); +} + +void AudioLimiter::setThreshold(float threshold) { + _impl->setThreshold(threshold); +} + +void AudioLimiter::setRelease(float release) { + _impl->setRelease(release); +} diff --git a/libraries/audio/src/AudioLimiter.h b/libraries/audio/src/AudioLimiter.h new file mode 100644 index 0000000000..96bfd610c2 --- /dev/null +++ b/libraries/audio/src/AudioLimiter.h @@ -0,0 +1,30 @@ +// +// AudioLimiter.h +// libraries/audio/src +// +// Created by Ken Cooke on 2/11/15. +// Copyright 2016 High Fidelity, Inc. +// + +#ifndef hifi_AudioLimiter_h +#define hifi_AudioLimiter_h + +#include "stdint.h" + +class LimiterImpl; + +class AudioLimiter { +public: + AudioLimiter(int sampleRate, int numChannels); + ~AudioLimiter(); + + void render(float* input, int16_t* output, int numFrames); + + void setThreshold(float threshold); + void setRelease(float release); + +private: + LimiterImpl* _impl; +}; + +#endif // hifi_AudioLimiter_h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index bc1f614f79..44c3146b41 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -273,6 +273,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrresetAmbientSphere(); + sceneKeyLight->setAmbientMap(nullptr); sceneKeyLight->setColor(_previousKeyLightColor); sceneKeyLight->setIntensity(_previousKeyLightIntensity); sceneKeyLight->setAmbientIntensity(_previousKeyLightAmbientIntensity); @@ -321,10 +322,11 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getKeyLightProperties().getAmbientURL(), CUBE_TEXTURE); - if (_ambientTexture->getGPUTexture()) { + if (_ambientTexture && _ambientTexture->isLoaded() && _ambientTexture->getGPUTexture()) { _pendingAmbientTexture = false; if (_ambientTexture->getGPUTexture()->getIrradiance()) { sceneKeyLight->setAmbientSphere(_ambientTexture->getGPUTexture()->getIrradiance()); + sceneKeyLight->setAmbientMap(_ambientTexture->getGPUTexture()); isAmbientTextureSet = true; } } else { @@ -354,12 +356,13 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); - if (_skyboxTexture->getGPUTexture()) { + if (_skyboxTexture && _skyboxTexture->isLoaded() && _skyboxTexture->getGPUTexture()) { auto texture = _skyboxTexture->getGPUTexture(); skybox->setCubemap(texture); _pendingSkyboxTexture = false; if (!isAmbientTextureSet && texture->getIrradiance()) { sceneKeyLight->setAmbientSphere(texture->getIrradiance()); + sceneKeyLight->setAmbientMap(texture); isAmbientTextureSet = true; } } else { @@ -381,6 +384,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrresetAmbientSphere(); + sceneKeyLight->setAmbientMap(nullptr); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e3870705c9..71b04d06a1 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -336,6 +336,33 @@ bool RenderableModelEntityItem::getAnimationFrame() { return newFrame; } +void RenderableModelEntityItem::updateModelBounds() { + if (!hasModel() || !_model) { + return; + } + bool movingOrAnimating = isMovingRelativeToParent() || isAnimatingSomething(); + if ((movingOrAnimating || + _needsInitialSimulation || + _model->getTranslation() != getPosition() || + _model->getRotation() != getRotation() || + _model->getRegistrationPoint() != getRegistrationPoint()) + && _model->isActive() && _dimensionsInitialized) { + _model->setScaleToFit(true, getDimensions()); + _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + + // make sure to simulate so everything gets set up correctly for rendering + { + PerformanceTimer perfTimer("_model->simulate"); + _model->simulate(0.0f); + } + + _needsInitialSimulation = false; + } +} + + // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items, and it handles // the per frame simulation/update that might be required if the models properties changed. void RenderableModelEntityItem::render(RenderArgs* args) { @@ -414,27 +441,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } } }); - - bool movingOrAnimating = isMovingRelativeToParent() || isAnimatingSomething(); - if ((movingOrAnimating || - _needsInitialSimulation || - _model->getTranslation() != getPosition() || - _model->getRotation() != getRotation() || - _model->getRegistrationPoint() != getRegistrationPoint()) - && _model->isActive() && _dimensionsInitialized) { - _model->setScaleToFit(true, getDimensions()); - _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - - // make sure to simulate so everything gets set up correctly for rendering - { - PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - } - - _needsInitialSimulation = false; - } + updateModelBounds(); } } } else { @@ -598,7 +605,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { if (type != SHAPE_TYPE_COMPOUND) { ModelEntityItem::computeShapeInfo(info); info.setParams(type, 0.5f * getDimensions()); + adjustShapeInfoByRegistration(info); } else { + updateModelBounds(); const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); // should never fall in here when collision model not fully loaded @@ -690,10 +699,13 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { AABox box; for (int i = 0; i < _points.size(); i++) { for (int j = 0; j < _points[i].size(); j++) { - // compensate for registraion + // compensate for registration _points[i][j] += _model->getOffset(); // scale so the collision points match the model points _points[i][j] *= scale; + // this next subtraction is done so we can give info the offset, which will cause + // the shape-key to change. + _points[i][j] -= _model->getOffset(); box += _points[i][j]; } } @@ -701,6 +713,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 collisionModelDimensions = box.getDimensions(); info.setParams(type, collisionModelDimensions, _compoundShapeURL); info.setConvexHulls(_points); + info.setOffset(_model->getOffset()); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index d6deaa5d8d..69c1c13151 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -49,6 +49,7 @@ public: virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; + void updateModelBounds(); virtual void render(RenderArgs* args) override; virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ef777df403..5118664268 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -142,7 +142,7 @@ glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units bool success; // TODO -- Does this actually have to happen in world space? - glm::vec3 center = getCenterPosition(success); + glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes glm::vec3 position = getPosition(success); glm::vec3 positionToCenter = center - position; @@ -430,6 +430,13 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const { return SHAPE_TYPE_COMPOUND; } +void RenderablePolyVoxEntityItem::updateRegistrationPoint(const glm::vec3& value) { + if (value != _registrationPoint) { + _meshDirty = true; + EntityItem::updateRegistrationPoint(value); + } +} + bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { _meshLock.lockForRead(); if (_meshDirty) { @@ -1224,10 +1231,16 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() { } glm::vec3 collisionModelDimensions = box.getDimensions(); - QByteArray b64 = _voxelData.toBase64(); + // include the registrationPoint in the shape key, because the offset is already + // included in the points and the shapeManager wont know that the shape has changed. + QString shapeKey = QString(_voxelData.toBase64()) + "," + + QString::number(_registrationPoint.x) + "," + + QString::number(_registrationPoint.y) + "," + + QString::number(_registrationPoint.z); _shapeInfoLock.lockForWrite(); - _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, QString(b64)); + _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); _shapeInfo.setConvexHulls(points); + // adjustShapeInfoByRegistration(_shapeInfo); _shapeInfoLock.unlock(); _meshLock.lockForWrite(); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index fdbaefb0c3..b40507f36a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -116,6 +116,8 @@ public: virtual void rebakeMesh(); + virtual void updateRegistrationPoint(const glm::vec3& value); + private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf index fc659d5928..9b7193bbfc 100644 --- a/libraries/entities-renderer/src/paintStroke.slf +++ b/libraries/entities-renderer/src/paintStroke.slf @@ -15,7 +15,7 @@ <@include DeferredBufferWrite.slh@> -// the diffuse texture +// the albedo texture uniform sampler2D originalTexture; // the interpolated normal diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index 2bdecbf9fe..a77049b733 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -28,9 +28,6 @@ void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; @@ -44,5 +41,5 @@ void main(void) { vec3 yzDiffuseScaled = yzDiffuse.rgb * abs(worldNormal.x); vec4 diffuse = vec4(xyDiffuseScaled + xzDiffuseScaled + yzDiffuseScaled, 1.0); - packDeferredFragment(_normal, 1.0, vec3(diffuse), specular, shininess); + packDeferredFragment(_normal, 1.0, vec3(diffuse), DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, DEFAULT_OCCLUSION); } diff --git a/libraries/entities-renderer/src/untextured_particle.slv b/libraries/entities-renderer/src/untextured_particle.slv index ab0ea15219..85f9d438bf 100644 --- a/libraries/entities-renderer/src/untextured_particle.slv +++ b/libraries/entities-renderer/src/untextured_particle.slv @@ -16,7 +16,7 @@ out vec4 _color; void main(void) { - // pass along the diffuse color + // pass along the color _color = colorToLinearRGBA(inColor); TransformCamera cam = getTransformCamera(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 04ca7559a0..c421f3826d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -677,7 +677,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, updateRegistrationPoint); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); @@ -1120,7 +1120,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { // these (along with "position" above) affect tree structure SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensions); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, updateRegistrationPoint); // these (along with all properties above) affect the simulation SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity); @@ -1340,6 +1340,15 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(getDimensions()); } +void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { + if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { + glm::mat4 scale = glm::scale(getDimensions()); + glm::mat4 registration = scale * glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); + glm::vec3 regTransVec = glm::vec3(registration[3]); // extract position component from matrix + info.setOffset(regTransVec); + } +} + bool EntityItem::contains(const glm::vec3& point) const { if (getShapeType() == SHAPE_TYPE_COMPOUND) { bool success; @@ -1348,12 +1357,21 @@ bool EntityItem::contains(const glm::vec3& point) const { } else { ShapeInfo info; info.setParams(getShapeType(), glm::vec3(0.5f)); + adjustShapeInfoByRegistration(info); return info.contains(worldToEntity(point)); } } void EntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(getShapeType(), 0.5f * getDimensions()); + adjustShapeInfoByRegistration(info); +} + +void EntityItem::updateRegistrationPoint(const glm::vec3& value) { + if (value != _registrationPoint) { + setRegistrationPoint(value); + _dirtyFlags |= Simulation::DIRTY_SHAPE; + } } void EntityItem::updatePosition(const glm::vec3& value) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 975f571eb3..62cc8ad69a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -305,6 +305,7 @@ public: // TODO: get rid of users of getRadius()... float getRadius() const; + virtual void adjustShapeInfoByRegistration(ShapeInfo& info) const; virtual bool contains(const glm::vec3& point) const; virtual bool isReadyToComputeShape() { return !isDead(); } @@ -319,6 +320,7 @@ public: virtual void setRotation(glm::quat orientation) { setOrientation(orientation); } // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags + virtual void updateRegistrationPoint(const glm::vec3& value); void updatePosition(const glm::vec3& value); void updatePositionFromNetwork(const glm::vec3& value); void updateDimensions(const glm::vec3& value); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index d7a47136b5..26f73eb65a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -12,6 +12,7 @@ #include "EntityItemID.h" #include +#include #include "EntitiesLogging.h" #include "EntityActionFactoryInterface.h" @@ -1063,6 +1064,34 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { return result; } +QVector EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex) { + QVector result; + if (!_entityTree) { + return result; + } + _entityTree->withReadLock([&] { + QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + return; + } + bool success; + SpatiallyNestableWeakPointer parentWP = parentFinder->find(parentID, success); + if (!success) { + return; + } + SpatiallyNestablePointer parent = parentWP.lock(); + if (!parent) { + return; + } + parent->forEachChild([&](SpatiallyNestablePointer child) { + if (child->getParentJointIndex() == jointIndex) { + result.push_back(child->getID()); + } + }); + }); + return result; +} + float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { return std::abs(mass * (newVelocity - oldVelocity)); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index fef000cc3d..79d8f0a0b0 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -168,7 +168,7 @@ public slots: Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); - + Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index bdf27f4440..6bf25f767d 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -18,51 +18,69 @@ #include "EntityItem.h" #include "EntitiesLogging.h" -const quint64 MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD = 2 * USECS_PER_SECOND; - -void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { - if (_entitiesWithSimulator.size() == 0) { - return; - } - - if (now < _nextSimulationExpiry) { - // nothing has expired yet - return; - } - - // If an Entity has a simulation owner but there has been no update for a while: clear the owner. - // If an Entity goes ownerless for too long: zero velocity and remove from _entitiesWithSimulator. - _nextSimulationExpiry = now + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD; +const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; +void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { QMutexLocker lock(&_mutex); - SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin(); - while (itemItr != _entitiesWithSimulator.end()) { + SetOfEntities::iterator itemItr = _entitiesWithSimulationOwner.begin(); + while (itemItr != _entitiesWithSimulationOwner.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getLastChangedOnServer() + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD; - if (expiry < now) { - if (entity->getSimulatorID().isNull()) { - // no simulators are volunteering - // zero the velocity on this entity so that it doesn't drift far away - entity->setVelocity(Vectors::ZERO); - entity->setAngularVelocity(Vectors::ZERO); - entity->setAcceleration(Vectors::ZERO); - // remove from list - itemItr = _entitiesWithSimulator.erase(itemItr); - continue; - } else { - // the simulator has stopped updating this object - // clear ownership and restart timer, giving nearby simulators time to volunteer - qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID(); - entity->clearSimulationOwnership(); + if (entity->getSimulatorID() == ownerID) { + // the simulator has abandonded this object --> remove from owned list + qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID(); + itemItr = _entitiesWithSimulationOwner.erase(itemItr); + + if (entity->getDynamic() && entity->hasLocalVelocity()) { + // it is still moving dynamically --> add to orphaned list + _entitiesThatNeedSimulationOwner.insert(entity); + quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + if (expiry < _nextOwnerlessExpiry) { + _nextOwnerlessExpiry = expiry; + } } + + // remove ownership and dirty all the tree elements that contain the it + entity->clearSimulationOwnership(); entity->markAsChangedOnServer(); - // dirty all the tree elements that contain the entity DirtyOctreeElementOperator op(entity->getElement()); getEntityTree()->recurseTreeWithOperator(&op); - } else if (expiry < _nextSimulationExpiry) { - _nextSimulationExpiry = expiry; + } else { + ++itemItr; + } + } +} + +void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { + if (now > _nextOwnerlessExpiry) { + // search for ownerless objects that have expired + QMutexLocker lock(&_mutex); + _nextOwnerlessExpiry = -1; + SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); + while (itemItr != _entitiesThatNeedSimulationOwner.end()) { + EntityItemPointer entity = *itemItr; + quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + if (expiry < now) { + // no simulators have volunteered ownership --> remove from list + itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); + + if (entity->getSimulatorID().isNull() && entity->getDynamic() && entity->hasLocalVelocity()) { + // zero the derivatives + entity->setVelocity(Vectors::ZERO); + entity->setAngularVelocity(Vectors::ZERO); + entity->setAcceleration(Vectors::ZERO); + + // dirty all the tree elements that contain it + entity->markAsChangedOnServer(); + DirtyOctreeElementOperator op(entity->getElement()); + getEntityTree()->recurseTreeWithOperator(&op); + } + } else { + ++itemItr; + if (expiry < _nextOwnerlessExpiry) { + _nextOwnerlessExpiry = expiry; + } + } } - ++itemItr; } } @@ -70,26 +88,47 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { EntitySimulation::addEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); - _entitiesWithSimulator.insert(entity); + _entitiesWithSimulationOwner.insert(entity); + } else if (entity->getDynamic() && entity->hasLocalVelocity()) { + QMutexLocker lock(&_mutex); + _entitiesThatNeedSimulationOwner.insert(entity); + quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + if (expiry < _nextOwnerlessExpiry) { + _nextOwnerlessExpiry = expiry; + } } } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { EntitySimulation::removeEntityInternal(entity); QMutexLocker lock(&_mutex); - _entitiesWithSimulator.remove(entity); + _entitiesWithSimulationOwner.remove(entity); + _entitiesThatNeedSimulationOwner.remove(entity); } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { EntitySimulation::changeEntityInternal(entity); - if (!entity->getSimulatorID().isNull()) { + if (entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); - _entitiesWithSimulator.insert(entity); + _entitiesWithSimulationOwner.remove(entity); + if (entity->getDynamic() && entity->hasLocalVelocity()) { + _entitiesThatNeedSimulationOwner.insert(entity); + quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + if (expiry < _nextOwnerlessExpiry) { + _nextOwnerlessExpiry = expiry; + } + } + } else { + QMutexLocker lock(&_mutex); + _entitiesWithSimulationOwner.insert(entity); + _entitiesThatNeedSimulationOwner.remove(entity); } entity->clearDirtyFlags(); } void SimpleEntitySimulation::clearEntitiesInternal() { - _entitiesWithSimulator.clear(); + QMutexLocker lock(&_mutex); + _entitiesWithSimulationOwner.clear(); + _entitiesThatNeedSimulationOwner.clear(); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 53a7574bf2..d9c04fdcf9 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -21,6 +21,8 @@ public: SimpleEntitySimulation() : EntitySimulation() { } virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); } + void clearOwnership(const QUuid& ownerID); + protected: virtual void updateEntitiesInternal(const quint64& now) override; virtual void addEntityInternal(EntityItemPointer entity) override; @@ -28,8 +30,9 @@ protected: virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void clearEntitiesInternal() override; - SetOfEntities _entitiesWithSimulator; - quint64 _nextSimulationExpiry { 0 }; + SetOfEntities _entitiesWithSimulationOwner; + SetOfEntities _entitiesThatNeedSimulationOwner; + quint64 _nextOwnerlessExpiry { 0 }; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1be3bbb5f6..500f856450 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -865,6 +865,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } else if (object.name == "Material") { FBXMaterial material; + if (object.properties.at(1).toByteArray().contains("StingrayPBS")) { + material.isPBSMaterial = true; + } foreach (const FBXNode& subobject, object.children) { bool properties = false; QByteArray propertyName; @@ -879,7 +882,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS propertyName = "P"; index = 4; } - if (properties) { + if (!material.isPBSMaterial && properties) { foreach (const FBXNode& property, subobject.children) { if (property.name == propertyName) { if (property.properties.at(0) == "DiffuseColor") { @@ -914,6 +917,44 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS #endif } } + } else if (material.isPBSMaterial && properties) { + std::vector unknowns; + foreach(const FBXNode& property, subobject.children) { + if (property.name == propertyName) { + if (property.properties.at(0) == "Maya|use_normal_map") { + material.useNormalMap = (bool)property.properties.at(index).value(); + + } else if (property.properties.at(0) == "Maya|base_color") { + material.diffuseColor = getVec3(property.properties, index); + } else if (property.properties.at(0) == "Maya|use_color_map") { + material.useAlbedoMap = (bool) property.properties.at(index).value(); + + } else if (property.properties.at(0) == "Maya|roughness") { + material.roughness = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Maya|use_roughness_map") { + material.useRoughnessMap = (bool)property.properties.at(index).value(); + + } else if (property.properties.at(0) == "Maya|metallic") { + material.metallic = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Maya|use_metallic_map") { + material.useMetallicMap = (bool)property.properties.at(index).value(); + + } else if (property.properties.at(0) == "Maya|emissive") { + material.emissiveColor = getVec3(property.properties, index); + } else if (property.properties.at(0) == "Maya|emissive_intensity") { + material.emissiveIntensity = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Maya|use_emissive_map") { + material.useEmissiveMap = (bool)property.properties.at(index).value(); + + } else if (property.properties.at(0) == "Maya|use_ao_map") { + material.useOcclusionMap = (bool)property.properties.at(index).value(); + + } else { + const QString propname = property.properties.at(0).toString(); + unknowns.push_back(propname.toStdString()); + } + } + } } #if defined(DEBUG_FBXREADER) else { @@ -1030,18 +1071,35 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (connection.properties.at(0) == "OP") { int counter = 0; QByteArray type = connection.properties.at(3).toByteArray().toLower(); - if (type.contains("diffuse")) { + if ((type.contains("diffuse") && !type.contains("tex_global_diffuse"))) { + diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("tex_color_map")) { diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } 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)); + transparentTextures.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")) { normalTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type.contains("specular") || type.contains("reflection")) { + } else if (type.contains("tex_normal_map")) { + normalTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if ((type.contains("specular") && !type.contains("tex_global_specular")) || type.contains("reflection")) { specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("tex_metallic_map")) { + metallicTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("shininess")) { + shininessTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("tex_roughness_map")) { + roughnessTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("emissive")) { + emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("tex_emissive_map")) { + emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("ambient")) { + ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("tex_ao_map")) { + occlusionTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type == "lcl rotation") { localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); @@ -1055,14 +1113,6 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } else if (type == "d|z") { zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type.contains("shininess")) { - counter++; - - } else if (_loadLightmaps && type.contains("emissive")) { - emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - - } else if (_loadLightmaps && type.contains("ambient")) { - ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else { QString typenam = type.data(); counter++; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 51cb5baf9f..3fefd837f3 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -111,7 +111,7 @@ public: QString texcoordSetName; bool isBumpmap{ false }; - + bool isNull() const { return name.isEmpty() && filename.isEmpty() && content.isEmpty(); } }; @@ -130,11 +130,10 @@ class FBXMaterial { public: FBXMaterial() {}; FBXMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, - const glm::vec2& emissiveParams, float shininess, float opacity) : + float shininess, float opacity) : diffuseColor(diffuseColor), specularColor(specularColor), emissiveColor(emissiveColor), - emissiveParams(emissiveParams), shininess(shininess), opacity(opacity) {} @@ -144,18 +143,39 @@ public: float specularFactor = 1.0f; glm::vec3 emissiveColor{ 0.0f }; - glm::vec2 emissiveParams{ 0.0f, 1.0f }; float shininess = 23.0f; float opacity = 1.0f; + float metallic{ 0.0f }; + float roughness{ 1.0f }; + float emissiveIntensity{ 1.0f }; + QString materialID; model::MaterialPointer _material; - FBXTexture diffuseTexture; - FBXTexture opacityTexture; FBXTexture normalTexture; + FBXTexture albedoTexture; + FBXTexture opacityTexture; + FBXTexture glossTexture; + FBXTexture roughnessTexture; FBXTexture specularTexture; + FBXTexture metallicTexture; FBXTexture emissiveTexture; + FBXTexture occlusionTexture; + FBXTexture lightmapTexture; + glm::vec2 lightmapParams{ 0.0f, 1.0f }; + + + bool isPBSMaterial{ false }; + // THe use XXXMap are not really used to drive which map are going or not, debug only + bool useNormalMap{ false }; + bool useAlbedoMap{ false }; + bool useOpacityMap{ false }; + bool useRoughnessMap{ false }; + bool useSpecularMap{ false }; + bool useMetallicMap{ false }; + bool useEmissiveMap{ false }; + bool useOcclusionMap{ false }; bool needTangentSpace() const; }; @@ -397,11 +417,16 @@ public: QHash diffuseTextures; + QHash transparentTextures; QHash bumpTextures; QHash normalTextures; QHash specularTextures; + QHash metallicTextures; + QHash roughnessTextures; + QHash shininessTextures; QHash emissiveTextures; QHash ambientTextures; + QHash occlusionTextures; QHash _fbxMaterials; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index e947a0356e..d87eac405f 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -79,11 +79,20 @@ void FBXReader::consolidateFBXMaterials() { } } - material.diffuseTexture = diffuseTexture; - + material.albedoTexture = diffuseTexture; detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); } - + + + FBXTexture transparentTexture; + QString transparentTextureID = transparentTextures.value(material.materialID); + if (!transparentTextureID.isNull()) { + transparentTexture = getTexture(transparentTextureID); + + material.opacityTexture = transparentTexture; + detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity()); + } + FBXTexture normalTexture; QString bumpTextureID = bumpTextures.value(material.materialID); QString normalTextureID = normalTextures.value(material.materialID); @@ -100,37 +109,66 @@ void FBXReader::consolidateFBXMaterials() { material.normalTexture = normalTexture; detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity()); } - - + FBXTexture specularTexture; QString specularTextureID = specularTextures.value(material.materialID); if (!specularTextureID.isNull()) { specularTexture = getTexture(specularTextureID); detectDifferentUVs |= (specularTexture.texcoordSet != 0) || (!specularTexture.transform.isIdentity()); - material.specularTexture = specularTexture; } + FBXTexture metallicTexture; + QString metallicTextureID = metallicTextures.value(material.materialID); + if (!metallicTextureID.isNull()) { + metallicTexture = getTexture(metallicTextureID); + detectDifferentUVs |= (metallicTexture.texcoordSet != 0) || (!metallicTexture.transform.isIdentity()); + material.metallicTexture = metallicTexture; + } + + FBXTexture roughnessTexture; + QString roughnessTextureID = roughnessTextures.value(material.materialID); + if (!roughnessTextureID.isNull()) { + roughnessTexture = getTexture(roughnessTextureID); + material.roughnessTexture = roughnessTexture; + detectDifferentUVs |= (roughnessTexture.texcoordSet != 0) || (!roughnessTexture.transform.isIdentity()); + } + + FBXTexture shininessTexture; + QString shininessTextureID = shininessTextures.value(material.materialID); + if (!shininessTextureID.isNull()) { + shininessTexture = getTexture(shininessTextureID); + material.glossTexture = shininessTexture; + detectDifferentUVs |= (shininessTexture.texcoordSet != 0) || (!shininessTexture.transform.isIdentity()); + } + FBXTexture emissiveTexture; - glm::vec2 emissiveParams(0.f, 1.f); - emissiveParams.x = _lightmapOffset; - emissiveParams.y = _lightmapLevel; - QString emissiveTextureID = emissiveTextures.value(material.materialID); - QString ambientTextureID = ambientTextures.value(material.materialID); - if (_loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) { - - if (!emissiveTextureID.isNull()) { - emissiveTexture = getTexture(emissiveTextureID); - emissiveParams.y = 4.0f; - } else if (!ambientTextureID.isNull()) { - emissiveTexture = getTexture(ambientTextureID); - } - - material.emissiveParams = emissiveParams; - material.emissiveTexture = emissiveTexture; - + if (!emissiveTextureID.isNull()) { + emissiveTexture = getTexture(emissiveTextureID); detectDifferentUVs |= (emissiveTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity()); + material.emissiveTexture = emissiveTexture; + } + + FBXTexture occlusionTexture; + QString occlusionTextureID = occlusionTextures.value(material.materialID); + if (!occlusionTextureID.isNull()) { + occlusionTexture = getTexture(occlusionTextureID); + detectDifferentUVs |= (occlusionTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity()); + material.occlusionTexture = occlusionTexture; + } + + glm::vec2 lightmapParams(0.f, 1.f); + lightmapParams.x = _lightmapOffset; + lightmapParams.y = _lightmapLevel; + + FBXTexture ambientTexture; + QString ambientTextureID = ambientTextures.value(material.materialID); + if (_loadLightmaps && !ambientTextureID.isNull()) { + ambientTexture = getTexture(ambientTextureID); + detectDifferentUVs |= (ambientTexture.texcoordSet != 0) || (!ambientTexture.transform.isIdentity()); + material.lightmapTexture = ambientTexture; + material.lightmapParams = lightmapParams; } // Finally create the true material representation @@ -140,13 +178,16 @@ void FBXReader::consolidateFBXMaterials() { auto diffuse = material.diffuseColor; // FIXME: Do not use the Diffuse Factor yet as some FBX models have it set to 0 // diffuse *= material.diffuseFactor; - material._material->setDiffuse(diffuse); + material._material->setAlbedo(diffuse); - float metallic = std::max(material.specularColor.x, std::max(material.specularColor.y, material.specularColor.z)); - // FIXME: Do not use the Specular Factor yet as some FBX models have it set to 0 - // metallic *= material.specularFactor; - material._material->setMetallic(metallic); - material._material->setGloss(material.shininess); + if (material.isPBSMaterial) { + material._material->setRoughness(material.roughness); + material._material->setMetallic(material.metallic); + } else { + material._material->setRoughness(model::Material::shininessToRoughness(material.shininess)); + float metallic = std::max(material.specularColor.x, std::max(material.specularColor.y, material.specularColor.z)); + material._material->setMetallic(metallic); + } if (material.opacity <= 0.0f) { material._material->setOpacity(1.0f); diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index ff33246b3f..e735ba06d9 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -558,7 +558,6 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, objMaterial.specularColor, glm::vec3(0.0f), - glm::vec2(0.0f, 1.0f), objMaterial.shininess, objMaterial.opacity); FBXMaterial& fbxMaterial = geometry.materials[materialID]; @@ -567,13 +566,13 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, model::MaterialPointer modelMaterial = fbxMaterial._material; if (!objMaterial.diffuseTextureFilename.isEmpty()) { - fbxMaterial.diffuseTexture.filename = objMaterial.diffuseTextureFilename; + fbxMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename; } modelMaterial->setEmissive(fbxMaterial.emissiveColor); - modelMaterial->setDiffuse(fbxMaterial.diffuseColor); + modelMaterial->setAlbedo(fbxMaterial.diffuseColor); modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); - modelMaterial->setGloss(fbxMaterial.shininess); + modelMaterial->setRoughness(model::Material::shininessToRoughness(fbxMaterial.shininess)); if (fbxMaterial.opacity <= 0.0f) { modelMaterial->setOpacity(1.0f); @@ -617,7 +616,7 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " specularColor =" << meshPart.specularColor << "mat =" << meshPart._material->getMetallic(); qCDebug(modelformat) << " emissiveColor =" << meshPart.emissiveColor << "mat =" << meshPart._material->getEmissive(); qCDebug(modelformat) << " emissiveParams =" << meshPart.emissiveParams; - qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getGloss(); + qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getRoughness(); qCDebug(modelformat) << " opacity =" << meshPart.opacity << "mat =" << meshPart._material->getOpacity(); */ qCDebug(modelformat) << " materialID =" << meshPart.materialID; diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index a66fc19458..7c603919fd 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -11,7 +11,7 @@ using namespace gpu; const Element Element::COLOR_RGBA_32{ VEC4, NUINT8, RGBA }; -const Element Element::COLOR_RGBA{ VEC4, FLOAT, RGBA }; +const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; const Element Element::VEC3F_XYZ{ VEC3, FLOAT, XYZ }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 54d40c3e12..625ca6cec3 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -247,7 +247,7 @@ public: } static const Element COLOR_RGBA_32; - static const Element COLOR_RGBA; + static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; static const Element VEC2F_XY; static const Element VEC3F_XYZ; diff --git a/libraries/gpu/src/gpu/GLBackendShader.cpp b/libraries/gpu/src/gpu/GLBackendShader.cpp index ca9c014c00..c27c5dd97d 100755 --- a/libraries/gpu/src/gpu/GLBackendShader.cpp +++ b/libraries/gpu/src/gpu/GLBackendShader.cpp @@ -162,8 +162,6 @@ GLBackend::GLShader* compileShader(const Shader& shader) { char* temp = new char[infoLength] ; glGetShaderInfoLog(glshader, infoLength, NULL, temp); - qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:"; - qCWarning(gpulogging) << temp; /* filestream.open("debugshader.glsl.info.txt"); @@ -172,6 +170,11 @@ GLBackend::GLShader* compileShader(const Shader& shader) { filestream.close(); } */ + + qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:"; + qCWarning(gpulogging) << srcstr; + qCWarning(gpulogging) << "GLShader::compileShader - errors:"; + qCWarning(gpulogging) << temp; delete[] temp; glDeleteShader(glshader); diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index d84d3e3967..efcafc9d60 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -145,6 +145,8 @@ public: switch(dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: + case gpu::SRGB: + case gpu::SRGBA: texel.internalFormat = GL_RED; switch (dstFormat.getType()) { case gpu::UINT32: { @@ -196,7 +198,11 @@ public: break; } case gpu::NUINT8: { - texel.internalFormat = GL_R8; + if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { + texel.internalFormat = GL_SLUMINANCE; + } else { + texel.internalFormat = GL_R8; + } break; } case gpu::NINT8: { @@ -209,6 +215,7 @@ public: } break; + case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 378f49c2f4..e05dc84c25 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -14,6 +14,7 @@ #include "Resource.h" #include //min max and more +#include #include @@ -139,6 +140,53 @@ protected: class Texture : public Resource { public: + class Usage { + public: + enum FlagBit { + COLOR = 0, // Texture is a color map + NORMAL, // Texture is a normal map + ALPHA, // Texture has an alpha channel + ALPHA_MASK, // Texture alpha channel is a Mask 0/1 + + NUM_FLAGS, + }; + typedef std::bitset Flags; + + // The key is the Flags + Flags _flags; + + Usage() : _flags(0) {} + Usage(const Flags& flags) : _flags(flags) {} + + bool operator== (const Usage& rhs) const { return _flags == rhs._flags; } + bool operator!= (const Usage& rhs) const { return _flags != rhs._flags; } + + class Builder { + friend class Usage; + Flags _flags{ 0 }; + public: + Builder() {} + + Usage build() const { return Usage(_flags); } + + Builder& withColor() { _flags.set(COLOR); return (*this); } + Builder& withNormal() { _flags.set(NORMAL); return (*this); } + Builder& withAlpha() { _flags.set(ALPHA); return (*this); } + Builder& withAlphaMask() { _flags.set(ALPHA_MASK); return (*this); } + }; + Usage(const Builder& builder) : Usage(builder._flags) {} + + bool isColor() const { return _flags[COLOR]; } + bool isNormal() const { return _flags[NORMAL]; } + + bool isAlpha() const { return _flags[ALPHA]; } + bool isAlphaMask() const { return _flags[ALPHA_MASK]; } + + + bool operator==(const Usage& usage) { return (_flags == usage._flags); } + bool operator!=(const Usage& usage) { return (_flags != usage._flags); } + }; + class Pixels { public: Pixels() {} @@ -343,6 +391,10 @@ public: bool isDefined() const { return _defined; } + // Usage is a a set of flags providing Semantic about the usage of the Texture. + void setUsage(const Usage& usage) { _usage = usage; } + Usage getUsage() const { return _usage; } + // For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them availalbe with the texture bool generateIrradiance(); const SHPointer& getIrradiance(uint16 slice = 0) const { return _irradiance; } @@ -380,6 +432,8 @@ protected: Type _type = TEX_1D; + Usage _usage; + SHPointer _irradiance; bool _autoGenerateMips = false; bool _isIrradianceValid = false; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2f8e04890c..21eed5f59f 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -136,10 +136,13 @@ bool NetworkGeometry::isLoadedWithTextures() const { if (!_isLoadedWithTextures) { for (auto&& material : _materials) { - if ((material->diffuseTexture && !material->diffuseTexture->isLoaded()) || + if ((material->albedoTexture && !material->albedoTexture->isLoaded()) || (material->normalTexture && !material->normalTexture->isLoaded()) || - (material->specularTexture && !material->specularTexture->isLoaded()) || - (material->emissiveTexture && !material->emissiveTexture->isLoaded())) { + (material->roughnessTexture && !material->roughnessTexture->isLoaded()) || + (material->metallicTexture && !material->metallicTexture->isLoaded()) || + (material->occlusionTexture && !material->occlusionTexture->isLoaded()) || + (material->emissiveTexture && !material->emissiveTexture->isLoaded()) || + (material->lightmapTexture && !material->lightmapTexture->isLoaded())) { return false; } } @@ -154,15 +157,15 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u for (auto&& material : _materials) { auto networkMaterial = material->_material; auto oldTextureMaps = networkMaterial->getTextureMaps(); - if (material->diffuseTextureName == name) { - material->diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE); + if (material->albedoTextureName == name) { + material->albedoTexture = textureCache->getTexture(url, DEFAULT_TEXTURE); - auto diffuseMap = model::TextureMapPointer(new model::TextureMap()); - diffuseMap->setTextureSource(material->diffuseTexture->_textureSource); - diffuseMap->setTextureTransform( - oldTextureMaps[model::MaterialKey::DIFFUSE_MAP]->getTextureTransform()); + auto albedoMap = model::TextureMapPointer(new model::TextureMap()); + albedoMap->setTextureSource(material->albedoTexture->_textureSource); + albedoMap->setTextureTransform( + oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform()); - networkMaterial->setTextureMap(model::MaterialKey::DIFFUSE_MAP, diffuseMap); + networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); } else if (material->normalTextureName == name) { material->normalTexture = textureCache->getTexture(url); @@ -170,15 +173,31 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u normalMap->setTextureSource(material->normalTexture->_textureSource); networkMaterial->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap); - } else if (material->specularTextureName == name) { - material->specularTexture = textureCache->getTexture(url); + } else if (material->roughnessTextureName == name) { + // FIXME: If passing a gloss map instead of a roughmap how to say that ? looking for gloss in the name ? + material->roughnessTexture = textureCache->getTexture(url, ROUGHNESS_TEXTURE); + + auto roughnessMap = model::TextureMapPointer(new model::TextureMap()); + roughnessMap->setTextureSource(material->roughnessTexture->_textureSource); + + networkMaterial->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap); + } else if (material->metallicTextureName == name) { + // FIXME: If passing a specular map instead of a metallic how to say that ? looking for wtf in the name ? + material->metallicTexture = textureCache->getTexture(url, METALLIC_TEXTURE); auto glossMap = model::TextureMapPointer(new model::TextureMap()); - glossMap->setTextureSource(material->specularTexture->_textureSource); + glossMap->setTextureSource(material->metallicTexture->_textureSource); - networkMaterial->setTextureMap(model::MaterialKey::GLOSS_MAP, glossMap); + networkMaterial->setTextureMap(model::MaterialKey::METALLIC_MAP, glossMap); } else if (material->emissiveTextureName == name) { - material->emissiveTexture = textureCache->getTexture(url); + material->emissiveTexture = textureCache->getTexture(url, EMISSIVE_TEXTURE); + + auto emissiveMap = model::TextureMapPointer(new model::TextureMap()); + emissiveMap->setTextureSource(material->emissiveTexture->_textureSource); + + networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap); + } else if (material->lightmapTextureName == name) { + material->emissiveTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE); auto lightmapMap = model::TextureMapPointer(new model::TextureMap()); lightmapMap->setTextureSource(material->emissiveTexture->_textureSource); @@ -200,9 +219,14 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u QStringList NetworkGeometry::getTextureNames() const { QStringList result; for (auto&& material : _materials) { - if (!material->diffuseTextureName.isEmpty() && material->diffuseTexture) { - QString textureURL = material->diffuseTexture->getURL().toString(); - result << material->diffuseTextureName + ":\"" + textureURL + "\""; + if (!material->emissiveTextureName.isEmpty() && material->emissiveTexture) { + QString textureURL = material->emissiveTexture->getURL().toString(); + result << material->emissiveTextureName + ":\"" + textureURL + "\""; + } + + if (!material->albedoTextureName.isEmpty() && material->albedoTexture) { + QString textureURL = material->albedoTexture->getURL().toString(); + result << material->albedoTextureName + ":\"" + textureURL + "\""; } if (!material->normalTextureName.isEmpty() && material->normalTexture) { @@ -210,14 +234,24 @@ QStringList NetworkGeometry::getTextureNames() const { result << material->normalTextureName + ":\"" + textureURL + "\""; } - if (!material->specularTextureName.isEmpty() && material->specularTexture) { - QString textureURL = material->specularTexture->getURL().toString(); - result << material->specularTextureName + ":\"" + textureURL + "\""; + if (!material->roughnessTextureName.isEmpty() && material->roughnessTexture) { + QString textureURL = material->roughnessTexture->getURL().toString(); + result << material->roughnessTextureName + ":\"" + textureURL + "\""; } - if (!material->emissiveTextureName.isEmpty() && material->emissiveTexture) { - QString textureURL = material->emissiveTexture->getURL().toString(); - result << material->emissiveTextureName + ":\"" + textureURL + "\""; + if (!material->metallicTextureName.isEmpty() && material->metallicTexture) { + QString textureURL = material->metallicTexture->getURL().toString(); + result << material->metallicTextureName + ":\"" + textureURL + "\""; + } + + if (!material->occlusionTextureName.isEmpty() && material->occlusionTexture) { + QString textureURL = material->occlusionTexture->getURL().toString(); + result << material->occlusionTextureName + ":\"" + textureURL + "\""; + } + + if (!material->lightmapTextureName.isEmpty() && material->lightmapTexture) { + QString textureURL = material->lightmapTexture->getURL().toString(); + result << material->lightmapTextureName + ":\"" + textureURL + "\""; } } @@ -304,50 +338,84 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas return networkMesh; } -static NetworkMaterial* buildNetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) { - auto textureCache = DependencyManager::get(); - NetworkMaterial* networkMaterial = new NetworkMaterial(); +static model::TextureMapPointer setupNetworkTextureMap(NetworkGeometry* geometry, const QUrl& textureBaseUrl, + const FBXTexture& texture, TextureType type, + NetworkTexturePointer& networkTexture, QString& networkTextureName) { + auto textureCache = DependencyManager::get(); + + // If content is inline, cache it under the fbx file, not its base url + const auto baseUrl = texture.content.isEmpty() ? textureBaseUrl : QUrl(textureBaseUrl.url() + "/"); + const auto filename = baseUrl.resolved(QUrl(texture.filename)); + + networkTexture = textureCache->getTexture(filename, type, texture.content); + QObject::connect(networkTexture.data(), &NetworkTexture::networkTextureCreated, geometry, &NetworkGeometry::textureLoaded); + networkTextureName = texture.name; + + auto map = std::make_shared(); + map->setTextureSource(networkTexture->_textureSource); + return map; +} + +static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FBXMaterial& material, const QUrl& textureBaseUrl) { + NetworkMaterial* networkMaterial = new NetworkMaterial(); networkMaterial->_material = material._material; - if (!material.diffuseTexture.filename.isEmpty()) { - networkMaterial->diffuseTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.diffuseTexture.filename)), DEFAULT_TEXTURE, material.diffuseTexture.content); - networkMaterial->diffuseTextureName = material.diffuseTexture.name; - - auto diffuseMap = model::TextureMapPointer(new model::TextureMap()); - diffuseMap->setTextureSource(networkMaterial->diffuseTexture->_textureSource); - diffuseMap->setTextureTransform(material.diffuseTexture.transform); - - material._material->setTextureMap(model::MaterialKey::DIFFUSE_MAP, diffuseMap); + if (!material.albedoTexture.filename.isEmpty()) { + auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE, + networkMaterial->albedoTexture, networkMaterial->albedoTextureName); + albedoMap->setTextureTransform(material.albedoTexture.transform); + material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); } + 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; - - auto normalMap = model::TextureMapPointer(new model::TextureMap()); - normalMap->setTextureSource(networkMaterial->normalTexture->_textureSource); - - material._material->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap); + auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture, + (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE), + networkMaterial->normalTexture, networkMaterial->normalTextureName); + networkMaterial->_material->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap); } - if (!material.specularTexture.filename.isEmpty()) { - networkMaterial->specularTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.specularTexture.filename)), SPECULAR_TEXTURE, material.specularTexture.content); - networkMaterial->specularTextureName = material.specularTexture.name; - auto glossMap = model::TextureMapPointer(new model::TextureMap()); - glossMap->setTextureSource(networkMaterial->specularTexture->_textureSource); - - material._material->setTextureMap(model::MaterialKey::GLOSS_MAP, glossMap); + // Roughness first or gloss maybe + if (!material.roughnessTexture.filename.isEmpty()) { + auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE, + networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName); + material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap); + } else if (!material.glossTexture.filename.isEmpty()) { + auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.glossTexture, GLOSS_TEXTURE, + networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName); + material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap); } + + // Metallic first or specular maybe + + if (!material.metallicTexture.filename.isEmpty()) { + auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE, + networkMaterial->metallicTexture, networkMaterial->metallicTextureName); + material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap); + } else if (!material.specularTexture.filename.isEmpty()) { + + auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE, + networkMaterial->metallicTexture, networkMaterial->metallicTextureName); + material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap); + } + + if (!material.occlusionTexture.filename.isEmpty()) { + auto occlusionMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE, + networkMaterial->occlusionTexture, networkMaterial->occlusionTextureName); + material._material->setTextureMap(model::MaterialKey::OCCLUSION_MAP, occlusionMap); + } + if (!material.emissiveTexture.filename.isEmpty()) { - networkMaterial->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.emissiveTexture.filename)), LIGHTMAP_TEXTURE, material.emissiveTexture.content); - networkMaterial->emissiveTextureName = material.emissiveTexture.name; - - - auto lightmapMap = model::TextureMapPointer(new model::TextureMap()); - lightmapMap->setTextureSource(networkMaterial->emissiveTexture->_textureSource); - lightmapMap->setTextureTransform(material.emissiveTexture.transform); - lightmapMap->setLightmapOffsetScale(material.emissiveParams.x, material.emissiveParams.y); + auto emissiveMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE, + networkMaterial->emissiveTexture, networkMaterial->emissiveTextureName); + material._material->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap); + } + if (!material.lightmapTexture.filename.isEmpty()) { + auto lightmapMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE, + networkMaterial->lightmapTexture, networkMaterial->lightmapTextureName); + lightmapMap->setTextureTransform(material.lightmapTexture.transform); + lightmapMap->setLightmapOffsetScale(material.lightmapParams.x, material.lightmapParams.y); material._material->setTextureMap(model::MaterialKey::LIGHTMAP_MAP, lightmapMap); } @@ -368,7 +436,7 @@ void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) { QHash fbxMatIDToMatID; foreach(const FBXMaterial& material, _geometry->materials) { fbxMatIDToMatID[material.materialID] = _materials.size(); - _materials.emplace_back(buildNetworkMaterial(material, _textureBaseUrl)); + _materials.emplace_back(buildNetworkMaterial(this, material, _textureBaseUrl)); } @@ -414,3 +482,6 @@ const NetworkMaterial* NetworkGeometry::getShapeMaterial(int shapeID) { } } +void NetworkGeometry::textureLoaded(const QWeakPointer& networkTexture) { + numTextureLoaded++; +} diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index b2d81c5900..60f185f691 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -105,6 +105,9 @@ signals: // Fired when something went wrong. void onFailure(NetworkGeometry& networkGeometry, Error error); +public slots: + void textureLoaded(const QWeakPointer& networkTexture); + protected slots: void mappingRequestDone(const QByteArray& data); void mappingRequestError(QNetworkReply::NetworkError error); @@ -115,6 +118,7 @@ protected slots: void modelParseSuccess(FBXGeometry* geometry); void modelParseError(int error, QString str); + protected: void attemptRequestInternal(); void requestMapping(const QUrl& url); @@ -133,6 +137,7 @@ protected: QUrl _modelUrl; QVariantHash _mapping; QUrl _textureBaseUrl; + int numTextureLoaded = 0; Resource* _resource = nullptr; std::unique_ptr _geometry; // This should go away evenutally once we can put everything we need in the model::AssetPointer @@ -173,15 +178,23 @@ public: class NetworkMaterial { public: + model::MaterialPointer _material; - QString diffuseTextureName; - QSharedPointer diffuseTexture; - QString normalTextureName; - QSharedPointer normalTexture; - QString specularTextureName; - QSharedPointer specularTexture; QString emissiveTextureName; QSharedPointer emissiveTexture; + QString albedoTextureName; + QSharedPointer albedoTexture; + QString normalTextureName; + QSharedPointer normalTexture; + QString roughnessTextureName; + QSharedPointer roughnessTexture; + QString metallicTextureName; + QSharedPointer metallicTexture; + QString occlusionTextureName; + QSharedPointer occlusionTexture; + QString lightmapTextureName; + QSharedPointer lightmapTexture; + }; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 8201339e93..d49ff91abf 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -212,12 +212,23 @@ NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const { return TextureLoaderFunc(model::TextureUsage::createNormalTextureFromNormalImage); break; } + case ROUGHNESS_TEXTURE: { + return TextureLoaderFunc(model::TextureUsage::createRoughnessTextureFromImage); + break; + } + case GLOSS_TEXTURE: { + return TextureLoaderFunc(model::TextureUsage::createRoughnessTextureFromGlossImage); + break; + } + case SPECULAR_TEXTURE: { + return TextureLoaderFunc(model::TextureUsage::createMetallicTextureFromImage); + break; + } case CUSTOM_TEXTURE: { return _textureLoader; break; } case DEFAULT_TEXTURE: - case SPECULAR_TEXTURE: case EMISSIVE_TEXTURE: default: { return TextureLoaderFunc(model::TextureUsage::create2DTextureFromImage); @@ -335,10 +346,6 @@ void NetworkTexture::setImage(const QImage& image, void* voidTexture, int origin finishedLoading(true); - imageLoaded(image); -} - -void NetworkTexture::imageLoaded(const QImage& image) { - // nothing by default + emit networkTextureCreated(qWeakPointerCast (_self)); } diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 033b4bede6..af82d2a1ad 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -29,7 +29,20 @@ class NetworkTexture; typedef QSharedPointer NetworkTexturePointer; -enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, BUMP_TEXTURE, SPECULAR_TEXTURE, EMISSIVE_TEXTURE, CUBE_TEXTURE, LIGHTMAP_TEXTURE, CUSTOM_TEXTURE }; +enum TextureType { + DEFAULT_TEXTURE, + NORMAL_TEXTURE, + BUMP_TEXTURE, + SPECULAR_TEXTURE, + METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey + ROUGHNESS_TEXTURE, + GLOSS_TEXTURE, + EMISSIVE_TEXTURE, + CUBE_TEXTURE, + OCCLUSION_TEXTURE, + LIGHTMAP_TEXTURE, + CUSTOM_TEXTURE +}; /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache, public Dependency { @@ -83,8 +96,6 @@ private: gpu::TexturePointer _blueTexture; gpu::TexturePointer _blackTexture; gpu::TexturePointer _normalFittingTexture; - - QHash > _dilatableNetworkTextures; }; /// A simple object wrapper for an OpenGL texture. @@ -114,7 +125,11 @@ public: int getHeight() const { return _height; } TextureLoaderFunc getTextureLoader() const; - + +signals: + void networkTextureCreated(const QWeakPointer& self); + + protected: virtual void downloadFinished(const QByteArray& data) override; @@ -123,8 +138,6 @@ protected: // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... Q_INVOKABLE void setImage(const QImage& image, void* texture, int originalWidth, int originalHeight); - virtual void imageLoaded(const QImage& image); - private: TextureType _type; diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp index 8a016c4d77..1762b0a9fb 100755 --- a/libraries/model/src/model/Light.cpp +++ b/libraries/model/src/model/Light.cpp @@ -130,7 +130,7 @@ void Light::setShowContour(float show) { if (show <= 0.f) { show = 0.0f; } - editSchema()._control.w = show; + editSchema()._control.z = show; } void Light::setAmbientSphere(const gpu::SphericalHarmonics& sphere) { @@ -140,3 +140,16 @@ void Light::setAmbientSphere(const gpu::SphericalHarmonics& sphere) { void Light::setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset) { editSchema()._ambientSphere.assignPreset(preset); } + +void Light::setAmbientMap(gpu::TexturePointer ambientMap) { + _ambientMap = ambientMap; + if (ambientMap) { + setAmbientMapNumMips(_ambientMap->evalNumMips()); + } else { + setAmbientMapNumMips(0); + } +} + +void Light::setAmbientMapNumMips(uint16_t numMips) { + editSchema()._ambientMapNumMips = (float)numMips; +} diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index dafbf90397..3b320e08fd 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -97,7 +97,7 @@ public: // For editing purpose, show the light volume contour. // Set to non 0 to show it, the value is used as the intensity of the contour color void setShowContour(float show); - float getShowContour() const { return getSchema()._control.w; } + float getShowContour() const { return getSchema()._control.z; } // If the light has an ambient (Indirect) component, then the Ambientintensity can be used to control its contribution to the lighting void setAmbientIntensity(float intensity); @@ -108,6 +108,12 @@ public: const gpu::SphericalHarmonics& getAmbientSphere() const { return getSchema()._ambientSphere; } void setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset); + void setAmbientMap(gpu::TexturePointer ambientMap); + gpu::TexturePointer getAmbientMap() const { return _ambientMap; } + + void setAmbientMapNumMips(uint16_t numMips); + uint16_t getAmbientMapNumMips() const { return (uint16_t) getSchema()._ambientMapNumMips; } + // Schema to access the attribute values of the light class Schema { public: @@ -120,7 +126,8 @@ public: Vec4 _spot{0.0f, 0.0f, 0.0f, 0.0f}; Vec4 _shadow{0.0f}; - Vec4 _control{0.0f, 0.0f, 0.0f, 0.0f}; + float _ambientMapNumMips{ 0.0f }; + Vec3 _control{ 0.0f, 0.0f, 0.0f }; gpu::SphericalHarmonics _ambientSphere; }; @@ -133,6 +140,8 @@ protected: UniformBufferView _schemaBuffer; Transform _transform; + gpu::TexturePointer _ambientMap; + const Schema& getSchema() const { return _schemaBuffer.get(); } Schema& editSchema() { return _schemaBuffer.edit(); } diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index af1c251ccb..80804e3cf5 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -109,6 +109,10 @@ SphericalHarmonics getLightAmbientSphere(Light l) { return l._ambientSphere; } +float getLightAmbientMapNumMips(Light l) { + return l._control.x; +} + <@if GPU_FEATURE_PROFILE == GPU_CORE @> uniform lightBuffer { Light light; diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp index 1d0f6ee5d9..10fa15edd5 100755 --- a/libraries/model/src/model/Material.cpp +++ b/libraries/model/src/model/Material.cpp @@ -44,37 +44,51 @@ Material& Material::operator= (const Material& material) { Material::~Material() { } -void Material::setDiffuse(const Color& diffuse, bool isSRGB) { - _key.setDiffuse(glm::any(glm::greaterThan(diffuse, Color(0.0f)))); - _schemaBuffer.edit()._diffuse = (isSRGB ? ColorUtils::toLinearVec3(diffuse) : diffuse); -} - -void Material::setMetallic(float metallic) { - _key.setMetallic(metallic > 0.0f); - _schemaBuffer.edit()._metallic = glm::vec3(metallic); -} - void Material::setEmissive(const Color& emissive, bool isSRGB) { _key.setEmissive(glm::any(glm::greaterThan(emissive, Color(0.0f)))); + _schemaBuffer.edit()._key = (uint32) _key._flags.to_ulong(); _schemaBuffer.edit()._emissive = (isSRGB ? ColorUtils::toLinearVec3(emissive) : emissive); } -void Material::setGloss(float gloss) { - _key.setGloss((gloss > 0.0f)); - _schemaBuffer.edit()._gloss = gloss; -} - void Material::setOpacity(float opacity) { _key.setTransparent((opacity < 1.0f)); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); _schemaBuffer.edit()._opacity = opacity; } +void Material::setAlbedo(const Color& albedo, bool isSRGB) { + _key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f)))); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + _schemaBuffer.edit()._albedo = (isSRGB ? ColorUtils::toLinearVec3(albedo) : albedo); +} + +void Material::setRoughness(float roughness) { + roughness = std::min(1.0f, std::max(roughness, 0.0f)); + _key.setGlossy((roughness < 1.0f)); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + _schemaBuffer.edit()._roughness = roughness; +} + +void Material::setFresnel(const Color& fresnel, bool isSRGB) { + //_key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f)))); + _schemaBuffer.edit()._fresnel = (isSRGB ? ColorUtils::toLinearVec3(fresnel) : fresnel); +} + +void Material::setMetallic(float metallic) { + _key.setMetallic(metallic > 0.0f); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + _schemaBuffer.edit()._metallic = metallic; +} + + void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { if (textureMap) { _key.setMapChannel(channel, (true)); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); _textureMaps[channel] = textureMap; } else { _key.setMapChannel(channel, (false)); + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); _textureMaps.erase(channel); } } diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index cb5a285dba..b22f8a3f42 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -28,17 +28,18 @@ class MaterialKey { public: enum FlagBit { EMISSIVE_VAL_BIT = 0, - DIFFUSE_VAL_BIT, + ALBEDO_VAL_BIT, METALLIC_VAL_BIT, - GLOSS_VAL_BIT, + GLOSSY_VAL_BIT, TRANSPARENT_VAL_BIT, EMISSIVE_MAP_BIT, - DIFFUSE_MAP_BIT, + ALBEDO_MAP_BIT, METALLIC_MAP_BIT, - GLOSS_MAP_BIT, + ROUGHNESS_MAP_BIT, TRANSPARENT_MAP_BIT, NORMAL_MAP_BIT, + OCCLUSION_MAP_BIT, LIGHTMAP_MAP_BIT, NUM_FLAGS, @@ -47,11 +48,12 @@ public: enum MapChannel { EMISSIVE_MAP = 0, - DIFFUSE_MAP, + ALBEDO_MAP, METALLIC_MAP, - GLOSS_MAP, + ROUGHNESS_MAP, TRANSPARENT_MAP, NORMAL_MAP, + OCCLUSION_MAP, LIGHTMAP_MAP, NUM_MAP_CHANNELS, @@ -71,22 +73,23 @@ public: MaterialKey build() const { return MaterialKey(_flags); } Builder& withEmissive() { _flags.set(EMISSIVE_VAL_BIT); return (*this); } - Builder& withDiffuse() { _flags.set(DIFFUSE_VAL_BIT); return (*this); } + Builder& withAlbedo() { _flags.set(ALBEDO_VAL_BIT); return (*this); } Builder& withMetallic() { _flags.set(METALLIC_VAL_BIT); return (*this); } - Builder& withGloss() { _flags.set(GLOSS_VAL_BIT); return (*this); } + Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); } Builder& withTransparent() { _flags.set(TRANSPARENT_VAL_BIT); return (*this); } Builder& withEmissiveMap() { _flags.set(EMISSIVE_MAP_BIT); return (*this); } - Builder& withDiffuseMap() { _flags.set(DIFFUSE_MAP_BIT); return (*this); } + Builder& withAlbedoMap() { _flags.set(ALBEDO_MAP_BIT); return (*this); } Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); } - Builder& withGlossMap() { _flags.set(GLOSS_MAP_BIT); return (*this); } + Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); } Builder& withTransparentMap() { _flags.set(TRANSPARENT_MAP_BIT); return (*this); } Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); } + Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); } Builder& withLightmapMap() { _flags.set(LIGHTMAP_MAP_BIT); return (*this); } // Convenient standard keys that we will keep on using all over the place - static MaterialKey opaqueDiffuse() { return Builder().withDiffuse().build(); } + static MaterialKey opaqueAlbedo() { return Builder().withAlbedo().build(); } }; void setEmissive(bool value) { _flags.set(EMISSIVE_VAL_BIT, value); } @@ -95,11 +98,11 @@ public: void setEmissiveMap(bool value) { _flags.set(EMISSIVE_MAP_BIT, value); } bool isEmissiveMap() const { return _flags[EMISSIVE_MAP_BIT]; } - void setDiffuse(bool value) { _flags.set(DIFFUSE_VAL_BIT, value); } - bool isDiffuse() const { return _flags[DIFFUSE_VAL_BIT]; } + void setAlbedo(bool value) { _flags.set(ALBEDO_VAL_BIT, value); } + bool isAlbedo() const { return _flags[ALBEDO_VAL_BIT]; } - void setDiffuseMap(bool value) { _flags.set(DIFFUSE_MAP_BIT, value); } - bool isDiffuseMap() const { return _flags[DIFFUSE_MAP_BIT]; } + void setAlbedoMap(bool value) { _flags.set(ALBEDO_MAP_BIT, value); } + bool isAlbedoMap() const { return _flags[ALBEDO_MAP_BIT]; } void setMetallic(bool value) { _flags.set(METALLIC_VAL_BIT, value); } bool isMetallic() const { return _flags[METALLIC_VAL_BIT]; } @@ -107,11 +110,12 @@ public: void setMetallicMap(bool value) { _flags.set(METALLIC_MAP_BIT, value); } bool isMetallicMap() const { return _flags[METALLIC_MAP_BIT]; } - void setGloss(bool value) { _flags.set(GLOSS_VAL_BIT, value); } - bool isGloss() const { return _flags[GLOSS_VAL_BIT]; } + void setGlossy(bool value) { _flags.set(GLOSSY_VAL_BIT, value); } + bool isGlossy() const { return _flags[GLOSSY_VAL_BIT]; } + bool isRough() const { return !_flags[GLOSSY_VAL_BIT]; } - void setGlossMap(bool value) { _flags.set(GLOSS_MAP_BIT, value); } - bool isGlossMap() const { return _flags[GLOSS_MAP_BIT]; } + void setRoughnessMap(bool value) { _flags.set(ROUGHNESS_MAP_BIT, value); } + bool isRoughnessMap() const { return _flags[ROUGHNESS_MAP_BIT]; } void setTransparent(bool value) { _flags.set(TRANSPARENT_VAL_BIT, value); } bool isTransparent() const { return _flags[TRANSPARENT_VAL_BIT]; } @@ -123,6 +127,9 @@ public: void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); } bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; } + void setOcclusionMap(bool value) { _flags.set(OCCLUSION_MAP_BIT, value); } + bool isOcclusionMap() const { return _flags[OCCLUSION_MAP_BIT]; } + void setLightmapMap(bool value) { _flags.set(LIGHTMAP_MAP_BIT, value); } bool isLightmapMap() const { return _flags[LIGHTMAP_MAP_BIT]; } @@ -154,11 +161,11 @@ public: Builder& withoutEmissiveMap() { _value.reset(MaterialKey::EMISSIVE_MAP_BIT); _mask.set(MaterialKey::EMISSIVE_MAP_BIT); return (*this); } Builder& withEmissiveMap() { _value.set(MaterialKey::EMISSIVE_MAP_BIT); _mask.set(MaterialKey::EMISSIVE_MAP_BIT); return (*this); } - Builder& withoutDiffuse() { _value.reset(MaterialKey::DIFFUSE_VAL_BIT); _mask.set(MaterialKey::DIFFUSE_VAL_BIT); return (*this); } - Builder& withDiffuse() { _value.set(MaterialKey::DIFFUSE_VAL_BIT); _mask.set(MaterialKey::DIFFUSE_VAL_BIT); return (*this); } + Builder& withoutAlbedo() { _value.reset(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); } + Builder& withAlbedo() { _value.set(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); } - Builder& withoutDiffuseMap() { _value.reset(MaterialKey::DIFFUSE_MAP_BIT); _mask.set(MaterialKey::DIFFUSE_MAP_BIT); return (*this); } - Builder& withDiffuseMap() { _value.set(MaterialKey::DIFFUSE_MAP_BIT); _mask.set(MaterialKey::DIFFUSE_MAP_BIT); return (*this); } + Builder& withoutAlbedoMap() { _value.reset(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); } + Builder& withAlbedoMap() { _value.set(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); } Builder& withoutMetallic() { _value.reset(MaterialKey::METALLIC_VAL_BIT); _mask.set(MaterialKey::METALLIC_VAL_BIT); return (*this); } Builder& withMetallic() { _value.set(MaterialKey::METALLIC_VAL_BIT); _mask.set(MaterialKey::METALLIC_VAL_BIT); return (*this); } @@ -166,11 +173,11 @@ public: Builder& withoutMetallicMap() { _value.reset(MaterialKey::METALLIC_MAP_BIT); _mask.set(MaterialKey::METALLIC_MAP_BIT); return (*this); } Builder& withMetallicMap() { _value.set(MaterialKey::METALLIC_MAP_BIT); _mask.set(MaterialKey::METALLIC_MAP_BIT); return (*this); } - Builder& withoutGloss() { _value.reset(MaterialKey::GLOSS_VAL_BIT); _mask.set(MaterialKey::GLOSS_VAL_BIT); return (*this); } - Builder& withGloss() { _value.set(MaterialKey::GLOSS_VAL_BIT); _mask.set(MaterialKey::GLOSS_VAL_BIT); return (*this); } + Builder& withoutGlossy() { _value.reset(MaterialKey::GLOSSY_VAL_BIT); _mask.set(MaterialKey::GLOSSY_VAL_BIT); return (*this); } + Builder& withGlossy() { _value.set(MaterialKey::GLOSSY_VAL_BIT); _mask.set(MaterialKey::GLOSSY_VAL_BIT); return (*this); } - Builder& withoutGlossMap() { _value.reset(MaterialKey::GLOSS_MAP_BIT); _mask.set(MaterialKey::GLOSS_MAP_BIT); return (*this); } - Builder& withGlossMap() { _value.set(MaterialKey::GLOSS_MAP_BIT); _mask.set(MaterialKey::GLOSS_MAP_BIT); return (*this); } + Builder& withoutRoughnessMap() { _value.reset(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); } + Builder& withRoughnessMap() { _value.set(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); } Builder& withoutTransparent() { _value.reset(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); } Builder& withTransparent() { _value.set(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); } @@ -181,11 +188,14 @@ public: Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } + Builder& withoutOcclusionMap() { _value.reset(MaterialKey::OCCLUSION_MAP_BIT); _mask.set(MaterialKey::OCCLUSION_MAP_BIT); return (*this); } + Builder& withOcclusionMap() { _value.set(MaterialKey::OCCLUSION_MAP_BIT); _mask.set(MaterialKey::OCCLUSION_MAP_BIT); return (*this); } + Builder& withoutLightmapMap() { _value.reset(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); } Builder& withLightmapMap() { _value.set(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); } // Convenient standard keys that we will keep on using all over the place - static MaterialFilter opaqueDiffuse() { return Builder().withDiffuse().withoutTransparent().build(); } + static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTransparent().build(); } }; // Item Filter operator testing if a key pass the filter @@ -223,29 +233,39 @@ public: void setEmissive(const Color& emissive, bool isSRGB = true); Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._emissive) : _schemaBuffer.get()._emissive); } - void setDiffuse(const Color& diffuse, bool isSRGB = true); - Color getDiffuse(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._diffuse) : _schemaBuffer.get()._diffuse); } - - void setMetallic(float metallic); - float getMetallic() const { return _schemaBuffer.get()._metallic.x; } - - void setGloss(float gloss); - float getGloss() const { return _schemaBuffer.get()._gloss; } - void setOpacity(float opacity); float getOpacity() const { return _schemaBuffer.get()._opacity; } + void setAlbedo(const Color& albedo, bool isSRGB = true); + Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._albedo) : _schemaBuffer.get()._albedo); } + + void setFresnel(const Color& fresnel, bool isSRGB = true); + Color getFresnel(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._fresnel) : _schemaBuffer.get()._fresnel); } + + void setMetallic(float metallic); + float getMetallic() const { return _schemaBuffer.get()._metallic; } + + void setRoughness(float roughness); + float getRoughness() const { return _schemaBuffer.get()._roughness; } + // Schema to access the attribute values of the material class Schema { public: - - glm::vec3 _diffuse{ 0.5f }; - float _opacity{1.f}; - glm::vec3 _metallic{ 0.03f }; - float _gloss{0.1f}; - glm::vec3 _emissive{ 0.0f }; - float _spare0{0.0f}; - glm::vec4 _spareVec4{0.0f}; // for alignment beauty, Material size == Mat4x4 + glm::vec3 _emissive{ 0.0f }; // No Emissive + float _opacity{ 1.0f }; // Opacity = 1 => Not Transparent + + glm::vec3 _albedo{ 0.5f }; // Grey albedo => isAlbedo + float _roughness{ 1.0f }; // Roughness = 1 => Not Glossy + + glm::vec3 _fresnel{ 0.03f }; // Fresnel value for a default non metallic + float _metallic{ 0.0f }; // Not Metallic + + + glm::vec3 _spare0{ 0.0f }; + + uint32_t _key{ 0 }; // a copy of the materialKey + + // for alignment beauty, Material size == Mat4x4 Schema() {} }; @@ -256,6 +276,9 @@ public: void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap); const TextureMaps& getTextureMaps() const { return _textureMaps; } + // conversion from legacy material properties to PBR equivalent + static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; } + protected: MaterialKey _key; diff --git a/libraries/model/src/model/Material.slh b/libraries/model/src/model/Material.slh index f48bdfa8f1..d75f3df5e0 100644 --- a/libraries/model/src/model/Material.slh +++ b/libraries/model/src/model/Material.slh @@ -12,9 +12,9 @@ <@def MODEL_MATERIAL_SLH@> struct Material { - vec4 _diffuse; - vec4 _specular; - vec4 _emissive; + vec4 _emissiveOpacity; + vec4 _albedoRoughness; + vec4 _fresnelMetallic; vec4 _spare; }; @@ -26,41 +26,36 @@ Material getMaterial() { return _mat; } - 0.04045 - // constants: - // T = 0.04045 - // A = 1 / 1.055 = 0.94786729857 - // B = 0.055 * A = 0.05213270142 - // C = 1 / 12.92 = 0.0773993808 - // G = 2.4 - const float T = 0.04045; - const float A = 0.947867; - const float B = 0.052132; - const float C = 0.077399; - const float G = 2.4; +vec3 getMaterialEmissive(Material m) { return m._emissiveOpacity.rgb; } +float getMaterialOpacity(Material m) { return m._emissiveOpacity.a; } - if (cs > T) { - return pow((cs * A + B), G); - } else { - return cs * C; - } -} +vec3 getMaterialAlbedo(Material m) { return m._albedoRoughness.rgb; } +float getMaterialRoughness(Material m) { return m._albedoRoughness.a; } + +vec3 getMaterialFresnel(Material m) { return m._fresnelMetallic.rgb; } +float getMaterialMetallic(Material m) { return m._fresnelMetallic.a; } + +float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); } + +int getMaterialKey(Material m) { return floatBitsToInt(m._spare.w); } + +const int EMISSIVE_VAL_BIT = 0x00000001; +const int ALBEDO_VAL_BIT = 0x00000002; +const int METALLIC_VAL_BIT = 0x00000004; +const int GLOSSY_VAL_BIT = 0x00000008; +const int TRANSPARENT_VAL_BIT = 0x00000010; + + +const int EMISSIVE_MAP_BIT = 0x00000020; +const int ALBEDO_MAP_BIT = 0x00000040; +const int METALLIC_MAP_BIT = 0x00000080; +const int ROUGHNESS_MAP_BIT = 0x00000100; +const int TRANSPARENT_MAP_BIT = 0x00000200; +const int NORMAL_MAP_BIT = 0x00000400; +const int OCCLUSION_MAP_BIT = 0x00000800; + +const int LIGHTMAP_MAP_BIT = 0x00001000; -vec3 SRGBToLinear(vec3 srgb) { - return vec3(componentSRGBToLinear(srgb.x),componentSRGBToLinear(srgb.y),componentSRGBToLinear(srgb.z)); -} -vec3 getMaterialDiffuse(Material m) { return (gl_FragCoord.x < 800 ? SRGBToLinear(m._diffuse.rgb) : m._diffuse.rgb); } -*/!> -float getMaterialOpacity(Material m) { return m._diffuse.a; } -vec3 getMaterialDiffuse(Material m) { return m._diffuse.rgb; } -vec3 getMaterialSpecular(Material m) { return m._specular.rgb; } -float getMaterialShininess(Material m) { return m._specular.a; } <@endif@> diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index c976e5c396..23503ef6fb 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -212,6 +212,9 @@ void SunSkyStage::setSunAmbientSphere(const gpu::SHPointer& sphere) { _sunLight->setAmbientSpherePreset(DEFAULT_AMBIENT_SPHERE); } } +void SunSkyStage::setSunAmbientMap(const gpu::TexturePointer& map) { + _sunLight->setAmbientMap(map); +} void SunSkyStage::setSunDirection(const Vec3& direction) { if (!isSunModelEnabled()) { diff --git a/libraries/model/src/model/Stage.h b/libraries/model/src/model/Stage.h index 31772d5e48..d9fa81b16f 100644 --- a/libraries/model/src/model/Stage.h +++ b/libraries/model/src/model/Stage.h @@ -150,6 +150,7 @@ public: void setSunAmbientIntensity(float intensity) { _sunLight->setAmbientIntensity(intensity); } float getSunAmbientIntensity() const { return getSunLight()->getAmbientIntensity(); } void setSunAmbientSphere(const gpu::SHPointer& sphere); + void setSunAmbientMap(const gpu::TexturePointer& map); // The sun direction is expressed in the world space void setSunDirection(const Vec3& direction); diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index cd42419f6a..d0416ec3b5 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -54,22 +54,38 @@ void TextureMap::setLightmapOffsetScale(float offset, float scale) { gpu::Texture* TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { QImage image = srcImage; bool validAlpha = false; + bool alphaAsMask = true; + const uint8 OPAQUE_ALPHA = 255; + const uint8 TRANSLUCENT_ALPHA = 0; if (image.hasAlphaChannel()) { + std::map alphaHistogram; + if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } - // Actual alpha channel? + // Actual alpha channel? create the histogram for (int y = 0; y < image.height(); ++y) { const QRgb* data = reinterpret_cast(image.constScanLine(y)); for (int x = 0; x < image.width(); ++x) { auto alpha = qAlpha(data[x]); - if (alpha != 255) { + alphaHistogram[alpha] ++; + if (alpha != OPAQUE_ALPHA) { validAlpha = true; break; } } } + + // If alpha was meaningfull refine + if (validAlpha && (alphaHistogram.size() > 1)) { + auto totalNumPixels = image.height() * image.width(); + auto numOpaques = alphaHistogram[OPAQUE_ALPHA]; + auto numTranslucents = alphaHistogram[TRANSLUCENT_ALPHA]; + auto numTransparents = totalNumPixels - numOpaques - numTranslucents; + + alphaAsMask = ((numTransparents / (double)totalNumPixels) < 0.05); + } } if (!validAlpha && image.format() != QImage::Format_RGB888) { @@ -89,10 +105,21 @@ gpu::Texture* TextureUsage::create2DTextureFromImage(const QImage& srcImage, con } theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + + auto usage = gpu::Texture::Usage::Builder().withColor(); + if (validAlpha) { + usage.withAlpha(); + if (alphaAsMask) { + usage.withAlphaMask(); + } + } + theTexture->setUsage(usage.build()); + theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); theTexture->autoGenerateMips(-1); // FIXME queue for transfer to GPU and block on completion + } return theTexture; @@ -223,6 +250,99 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm return theTexture; } +gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { + QImage image = srcImage; + if (!image.hasAlphaChannel()) { + if (image.format() != QImage::Format_RGB888) { + image = image.convertToFormat(QImage::Format_RGB888); + } + } else { + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + } + + image = image.convertToFormat(QImage::Format_Grayscale8); + + gpu::Texture* theTexture = nullptr; + if ((image.width() > 0) && (image.height() > 0)) { + + gpu::Element formatGPU = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); + + theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + theTexture->autoGenerateMips(-1); + + // FIXME queue for transfer to GPU and block on completion + } + + return theTexture; +} + +gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName) { + QImage image = srcImage; + if (!image.hasAlphaChannel()) { + if (image.format() != QImage::Format_RGB888) { + image = image.convertToFormat(QImage::Format_RGB888); + } + } else { + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + } + + // Gloss turned into Rough + image.invertPixels(QImage::InvertRgba); + + image = image.convertToFormat(QImage::Format_Grayscale8); + + gpu::Texture* theTexture = nullptr; + if ((image.width() > 0) && (image.height() > 0)) { + + gpu::Element formatGPU = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); + + theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + theTexture->autoGenerateMips(-1); + + // FIXME queue for transfer to GPU and block on completion + } + + return theTexture; +} + +gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { + QImage image = srcImage; + if (!image.hasAlphaChannel()) { + if (image.format() != QImage::Format_RGB888) { + image = image.convertToFormat(QImage::Format_RGB888); + } + } else { + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + } + + image = image.convertToFormat(QImage::Format_Grayscale8); + + gpu::Texture* theTexture = nullptr; + if ((image.width() > 0) && (image.height() > 0)) { + + gpu::Element formatGPU = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); + + theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + theTexture->autoGenerateMips(-1); + + // FIXME queue for transfer to GPU and block on completion + } + + return theTexture; +} + class CubeLayout { public: int _widthRatio = 1; @@ -473,8 +593,8 @@ gpu::Texture* TextureUsage::createCubeTextureFromImage(const QImage& srcImage, c theTexture->assignStoredMipFace(0, formatMip, face.byteCount(), face.constBits(), f); f++; } - - // GEnerate irradiance while we are at it + + // Generate irradiance while we are at it theTexture->generateIrradiance(); } } diff --git a/libraries/model/src/model/TextureMap.h b/libraries/model/src/model/TextureMap.h index 6bc5b8228c..228adb25e6 100755 --- a/libraries/model/src/model/TextureMap.h +++ b/libraries/model/src/model/TextureMap.h @@ -27,13 +27,16 @@ typedef glm::vec3 Color; class TextureUsage { public: gpu::Texture::Type _type{ gpu::Texture::TEX_2D }; - Material::MapFlags _materialUsage{ MaterialKey::DIFFUSE_MAP }; + Material::MapFlags _materialUsage{ MaterialKey::ALBEDO_MAP }; int _environmentUsage = 0; static gpu::Texture* create2DTextureFromImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName); + static gpu::Texture* createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName); + static gpu::Texture* createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName); + static gpu::Texture* createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createCubeTextureFromImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 4ded2216d0..af30132b70 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -12,10 +12,13 @@ #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -60,13 +63,12 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co updateReciever(updateReceiver), updateSlot(updateSlot) { + } AccountManager::AccountManager() : _authURL(), - _pendingCallbackMap(), - _accountInfo(), - _shouldPersistToSettingsFile(true) + _pendingCallbackMap() { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -80,9 +82,6 @@ AccountManager::AccountManager() : qRegisterMetaType("QHttpMultiPart*"); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - - // once we have a profile in account manager make sure we generate a new keypair - connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewKeypair); } const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; @@ -93,16 +92,9 @@ void AccountManager::logout() { emit balanceChanged(0); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - - if (_shouldPersistToSettingsFile) { - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString; - Setting::Handle(path).remove(); - - qCDebug(networking) << "Removed account info for" << _authURL << "from in-memory accounts and .ini file"; - } else { - qCDebug(networking) << "Cleared data server account info in account manager."; - } + + // remove this account from the account settings file + removeAccountFromFile(); emit logoutComplete(); // the username has changed to blank @@ -124,35 +116,87 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) { emit balanceChanged(newBalance); } +QString accountFileDir() { + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +} + +QString accountFilePath() { + return accountFileDir() + "/AccountInfo.bin"; +} + +QVariantMap accountMapFromFile(bool& success) { + QFile accountFile { accountFilePath() }; + + if (accountFile.open(QIODevice::ReadOnly)) { + // grab the current QVariantMap from the settings file + QDataStream readStream(&accountFile); + QVariantMap accountMap; + + readStream >> accountMap; + + // close the file now that we have read the data + accountFile.close(); + + success = true; + + return accountMap; + } else { + // failed to open file, return empty QVariantMap + // there was only an error if the account file existed when we tried to load it + success = !accountFile.exists(); + + return QVariantMap(); + } +} + void AccountManager::setAuthURL(const QUrl& authURL) { if (_authURL != authURL) { _authURL = authURL; qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - if (_shouldPersistToSettingsFile) { + // check if there are existing access tokens to load from settings + QFile accountsFile { accountFilePath() }; + bool loadedMap = false; + auto accountsMap = accountMapFromFile(loadedMap); + + if (accountsFile.exists() && loadedMap) { + // pull out the stored account info and store it in memory + _accountInfo = accountsMap[_authURL.toString()].value(); + + qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString()); + } else { + // we didn't have a file - see if we can migrate old settings and store them in the new file + // check if there are existing access tokens to load from settings Settings settings; settings.beginGroup(ACCOUNTS_GROUP); - + foreach(const QString& key, settings.allKeys()) { // take a key copy to perform the double slash replacement QString keyCopy(key); QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//")); - + if (keyURL == _authURL) { // pull out the stored access token and store it in memory _accountInfo = settings.value(key).value(); - qCDebug(networking) << "Found a data-server access token for" << qPrintable(keyURL.toString()); - - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); - } + + qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString()) + << "from previous settings file"; } } + + if (_accountInfo.getAccessToken().token.isEmpty()) { + qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; + } else { + // persist the migrated settings to file + persistAccountToFile(); + } + } + + if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) { + // we are missing profile information, request it now + requestProfile(); } // tell listeners that the auth endpoint has changed @@ -299,9 +343,11 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { } else { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received JSON response from data-server that has no matching callback."; + qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll()); } + + requestReply->deleteLater(); } } @@ -317,22 +363,74 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { _pendingCallbackMap.remove(requestReply); } else { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received error response from data-server that has no matching callback."; + qCDebug(networking) << "Received error response from metaverse API that has no matching callback."; qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString(); qCDebug(networking) << requestReply->readAll(); } + + requestReply->deleteLater(); } } -void AccountManager::persistAccountToSettings() { - if (_shouldPersistToSettingsFile) { - // store this access token into the local settings - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString; - Setting::Handle(path).set(QVariant::fromValue(_accountInfo)); +bool writeAccountMapToFile(const QVariantMap& accountMap) { + // re-open the file and truncate it + QFile accountFile { accountFilePath() }; + + // make sure the directory that will hold the account file exists + QDir accountFileDirectory { accountFileDir() }; + accountFileDirectory.mkpath("."); + + if (accountFile.open(QIODevice::WriteOnly)) { + QDataStream writeStream(&accountFile); + + // persist the updated account QVariantMap to file + writeStream << accountMap; + + // close the file with the newly persisted settings + accountFile.close(); + + return true; + } else { + return false; } } +void AccountManager::persistAccountToFile() { + + qCDebug(networking) << "Persisting AccountManager accounts to" << accountFilePath(); + + bool wasLoaded = false; + auto accountMap = accountMapFromFile(wasLoaded); + + if (wasLoaded) { + // replace the current account information for this auth URL in the account map + accountMap[_authURL.toString()] = QVariant::fromValue(_accountInfo); + + // re-open the file and truncate it + if (writeAccountMapToFile(accountMap)) { + return; + } + } + + qCWarning(networking) << "Could not load accounts file - unable to persist account information to file."; +} + +void AccountManager::removeAccountFromFile() { + bool wasLoaded = false; + auto accountMap = accountMapFromFile(wasLoaded); + + if (wasLoaded) { + accountMap.remove(_authURL.toString()); + if (writeAccountMapToFile(accountMap)) { + qCDebug(networking) << "Removed account info for" << _authURL << "from settings file."; + return; + } + } + + qCWarning(networking) << "Count not load accounts file - unable to remove account information for" << _authURL + << "from settings file."; +} + bool AccountManager::hasValidAccessToken() { if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { @@ -359,16 +457,19 @@ bool AccountManager::checkAndSignalForAccessToken() { } void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) { - // clear our current DataServerAccountInfo - _accountInfo = DataServerAccountInfo(); - - // start the new account info with a new OAuthAccessToken + // replace the account info access token with a new OAuthAccessToken OAuthAccessToken newOAuthToken; newOAuthToken.token = accessToken; - - qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2); + + if (!accessToken.isEmpty()) { + qCDebug(networking) << "Setting new AccountManager OAuth token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2); + } else if (!_accountInfo.getAccessToken().token.isEmpty()) { + qCDebug(networking) << "Clearing AccountManager OAuth token."; + } _accountInfo.setAccessToken(newOAuthToken); + + persistAccountToFile(); } void AccountManager::requestAccessToken(const QString& login, const QString& password) { @@ -423,7 +524,7 @@ void AccountManager::requestAccessTokenFinished() { emit loginComplete(rootURL); - persistAccountToSettings(); + persistAccountToFile(); requestProfile(); } @@ -469,7 +570,7 @@ void AccountManager::requestProfileFinished() { emit usernameChanged(_accountInfo.getUsername()); // store the whole profile into the local settings - persistAccountToSettings(); + persistAccountToFile(); } else { // TODO: error handling @@ -482,57 +583,141 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { qCDebug(networking) << "AccountManager requestProfileError - " << error; } -void AccountManager::generateNewKeypair() { - // setup a new QThread to generate the keypair on, in case it takes a while - QThread* generateThread = new QThread(this); - generateThread->setObjectName("Account Manager Generator Thread"); - - // setup a keypair generator - RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator(); - - connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair); - connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair); - connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, - this, &AccountManager::handleKeypairGenerationError); - connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); - connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); - - keypairGenerator->moveToThread(generateThread); - - qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA key-pair."; - generateThread->start(); +void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) { + + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "generateNewKeypair", Q_ARG(bool, isUserKeypair), Q_ARG(QUuid, domainID)); + return; + } + + if (!isUserKeypair && domainID.isNull()) { + qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair."; + return; + } + + // make sure we don't already have an outbound keypair generation request + if (!_isWaitingForKeypairResponse) { + _isWaitingForKeypairResponse = true; + + // clear the current private key + qDebug() << "Clearing current private key in DataServerAccountInfo"; + _accountInfo.setPrivateKey(QByteArray()); + + // setup a new QThread to generate the keypair on, in case it takes a while + QThread* generateThread = new QThread(this); + generateThread->setObjectName("Account Manager Generator Thread"); + + // setup a keypair generator + RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator; + + if (!isUserKeypair) { + keypairGenerator->setDomainID(domainID); + _accountInfo.setDomainID(domainID); + } + + // start keypair generation when the thread starts + connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair); + + // handle success or failure of keypair generation + connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair); + connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, + this, &AccountManager::handleKeypairGenerationError); + + connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); + connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); + + keypairGenerator->moveToThread(generateThread); + + qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair."; + generateThread->start(); + } } -void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) { +void AccountManager::processGeneratedKeypair() { - qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key."; - - // set the private key on our data-server account info - _accountInfo.setPrivateKey(privateKey); - persistAccountToSettings(); - - // upload the public key so data-web has an up-to-date key - const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; - - // setup a multipart upload to send up the public key - QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - - QHttpPart keyPart; - keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - keyPart.setBody(publicKey); - - requestMultiPart->append(keyPart); - - sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QByteArray(), requestMultiPart); - - // get rid of the keypair generator now that we don't need it anymore - sender()->deleteLater(); + qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now."; + + RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); + + if (keypairGenerator) { + // hold the private key to later set our metaverse API account info if upload succeeds + _pendingPrivateKey = keypairGenerator->getPrivateKey(); + + // upload the public key so data-web has an up-to-date key + const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; + const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; + + QString uploadPath; + if (keypairGenerator->getDomainID().isNull()) { + uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; + } else { + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID())); + } + + // setup a multipart upload to send up the public key + QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart keyPart; + keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + keyPart.setBody(keypairGenerator->getPublicKey()); + + requestMultiPart->append(keyPart); + + // setup callback parameters so we know once the keypair upload has succeeded or failed + JSONCallbackParameters callbackParameters; + callbackParameters.jsonCallbackReceiver = this; + callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded"; + callbackParameters.errorCallbackReceiver = this; + callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; + + sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, + callbackParameters, QByteArray(), requestMultiPart); + + keypairGenerator->deleteLater(); + } else { + qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator" + << "but the casted sender is NULL. Will not process generated keypair."; + } +} + +void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { + qDebug() << "Uploaded public key to Metaverse API. RSA keypair generation is completed."; + + // public key upload complete - store the matching private key and persist the account to settings + _accountInfo.setPrivateKey(_pendingPrivateKey); + _pendingPrivateKey.clear(); + persistAccountToFile(); + + // clear our waiting state + _isWaitingForKeypairResponse = false; + + emit newKeypair(); + + // delete the reply object now that we are done with it + reply.deleteLater(); +} + +void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) { + // the public key upload has failed + qWarning() << "Public key upload failed from AccountManager" << reply.errorString(); + + // we aren't waiting for a response any longer + _isWaitingForKeypairResponse = false; + + // clear our pending private key + _pendingPrivateKey.clear(); + + // delete the reply object now that we are done with it + reply.deleteLater(); } void AccountManager::handleKeypairGenerationError() { - // for now there isn't anything we do with this except get the worker thread to clean up + qCritical() << "Error generating keypair - this is likely to cause authentication issues."; + + // reset our waiting state for keypair response + _isWaitingForKeypairResponse = false; + sender()->deleteLater(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 719279b0cf..0ebefafbed 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -62,12 +62,12 @@ public: QHttpMultiPart* dataMultiPart = NULL, const QVariantMap& propertyMap = QVariantMap()); + void setIsAgent(bool isAgent) { _isAgent = isAgent; } + const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); bool hasAuthEndpoint() { return !_authURL.isEmpty(); } - void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; } - bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); @@ -87,7 +87,9 @@ public slots: void logout(); void updateBalance(); void accountInfoBalanceChanged(qint64 newBalance); - void generateNewKeypair(); + void generateNewUserKeypair() { generateNewKeypair(); } + void generateNewDomainKeypair(const QUuid& domainID) { generateNewKeypair(false, domainID); } + signals: void authRequired(); void authEndpointChanged(); @@ -97,25 +99,36 @@ signals: void loginFailed(); void logoutComplete(); void balanceChanged(qint64 newBalance); + void newKeypair(); + private slots: void processReply(); void handleKeypairGenerationError(); - void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + void processGeneratedKeypair(); + void publicKeyUploadSucceeded(QNetworkReply& reply); + void publicKeyUploadFailed(QNetworkReply& reply); + void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); + private: AccountManager(); - AccountManager(AccountManager const& other); // not implemented - void operator=(AccountManager const& other); // not implemented + AccountManager(AccountManager const& other) = delete; + void operator=(AccountManager const& other) = delete; - void persistAccountToSettings(); + void persistAccountToFile(); + void removeAccountFromFile(); void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); QUrl _authURL; + QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; - bool _shouldPersistToSettingsFile; + bool _isAgent { false }; + + bool _isWaitingForKeypairResponse { false }; + QByteArray _pendingPrivateKey; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 4e7e7f24ba..9591828fef 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -47,22 +47,53 @@ AssetClient::AssetClient() { } void AssetClient::init() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection); - } - + Q_ASSERT(QThread::currentThread() == thread()); + // Setup disk cache if not already - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + auto& networkAccessManager = NetworkAccessManager::getInstance(); if (!networkAccessManager.cache()) { QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); cachePath = !cachePath.isEmpty() ? cachePath : "interfaceCache"; - + QNetworkDiskCache* cache = new QNetworkDiskCache(); cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); cache->setCacheDirectory(cachePath); networkAccessManager.setCache(cache); - qCDebug(asset_client) << "AssetClient disk cache setup at" << cachePath - << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; + qDebug() << "ResourceManager disk cache setup at" << cachePath + << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; + } +} + + +void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection, + Q_ARG(QObject*, reciever), Q_ARG(QString, slot)); + return; + } + + + if (auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache())) { + QMetaObject::invokeMethod(reciever, slot.toStdString().data(), Qt::QueuedConnection, + Q_ARG(QString, cache->cacheDirectory()), + Q_ARG(qint64, cache->cacheSize()), + Q_ARG(qint64, cache->maximumCacheSize())); + } else { + qCWarning(asset_client) << "No disk cache to get info from."; + } +} + +void AssetClient::clearCache() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearCache", Qt::QueuedConnection); + return; + } + + if (auto cache = NetworkAccessManager::getInstance().cache()) { + qDebug() << "AssetClient::clearCache(): Clearing disk cache."; + cache->clear(); + } else { + qCWarning(asset_client) << "No disk cache to clear."; } } diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 58790ef926..f66fe8adcc 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -43,13 +43,17 @@ class AssetClient : public QObject, public Dependency { Q_OBJECT public: AssetClient(); - - Q_INVOKABLE void init(); Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension); Q_INVOKABLE AssetUpload* createUpload(const QString& filename); Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension); +public slots: + void init(); + + void cacheInfoRequest(QObject* reciever, QString slot); + void clearCache(); + private slots: void handleAssetGetInfoReply(QSharedPointer message, SharedNodePointer senderNode); void handleAssetGetReply(QSharedPointer message, SharedNodePointer senderNode); diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 0024426be0..4d2ea56ca4 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -20,7 +20,7 @@ class AssetResourceRequest : public ResourceRequest { Q_OBJECT public: - AssetResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + AssetResourceRequest(const QUrl& url) : ResourceRequest(url) { } ~AssetResourceRequest(); protected: diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 5d633a8df1..9455fb1b88 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -25,19 +25,6 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif -DataServerAccountInfo::DataServerAccountInfo() : - _accessToken(), - _username(), - _xmppPassword(), - _discourseApiKey(), - _walletID(), - _balance(0), - _hasBalance(false), - _privateKey() -{ - -} - DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; @@ -47,6 +34,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _balance = otherInfo._balance; _hasBalance = otherInfo._hasBalance; _privateKey = otherInfo._privateKey; + _domainID = otherInfo._domainID; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -66,6 +54,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_balance, otherInfo._balance); swap(_hasBalance, otherInfo._hasBalance); swap(_privateKey, otherInfo._privateKey); + swap(_domainID, otherInfo._domainID); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -128,59 +117,62 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject } QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) { - - if (!_privateKey.isEmpty()) { - const char* privateKeyData = _privateKey.constData(); - RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, - reinterpret_cast(&privateKeyData), - _privateKey.size()); - if (rsaPrivateKey) { - QByteArray lowercaseUsername = _username.toLower().toUtf8(); - QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), - QCryptographicHash::Sha256); - - QByteArray usernameSignature(RSA_size(rsaPrivateKey), 0); - unsigned int usernameSignatureSize = 0; - - int encryptReturn = RSA_sign(NID_sha256, - reinterpret_cast(usernameWithToken.constData()), - usernameWithToken.size(), - reinterpret_cast(usernameSignature.data()), - &usernameSignatureSize, - rsaPrivateKey); - - // free the private key RSA struct now that we are done with it - RSA_free(rsaPrivateKey); + auto lowercaseUsername = _username.toLower().toUtf8(); + auto plaintext = lowercaseUsername.append(connectionToken.toRfc4122()); - if (encryptReturn == -1) { - qCDebug(networking) << "Error encrypting username signature."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - } else { - qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); - return usernameSignature; - } - - } else { - qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - } - } - return QByteArray(); + auto signature = signPlaintext(plaintext); + if (!signature.isEmpty()) { + qDebug(networking) << "Returning username" << _username + << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); + } else { + qCDebug(networking) << "Error signing username with connection token"; + qCDebug(networking) << "Will re-attempt on next domain-server check in."; + } + + return signature; } -void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) { - _privateKey = privateKey; - +QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { + if (!_privateKey.isEmpty()) { + const char* privateKeyData = _privateKey.constData(); + RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, + reinterpret_cast(&privateKeyData), + _privateKey.size()); + if (rsaPrivateKey) { + QByteArray signature(RSA_size(rsaPrivateKey), 0); + unsigned int signatureBytes = 0; + + QByteArray hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); + + int encryptReturn = RSA_sign(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.data()), + &signatureBytes, + rsaPrivateKey); + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); + + if (encryptReturn != -1) { + return signature; + } + } else { + qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; + } + } + return QByteArray(); } QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey - << info._walletID << info._privateKey; + << info._walletID << info._privateKey << info._domainID; + return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey - >> info._walletID >> info._privateKey; + >> info._walletID >> info._privateKey >> info._domainID; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 9b80de5422..6223bc008e 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -23,7 +23,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f; class DataServerAccountInfo : public QObject { Q_OBJECT public: - DataServerAccountInfo(); + DataServerAccountInfo() {}; DataServerAccountInfo(const DataServerAccountInfo& otherInfo); DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); @@ -42,10 +42,6 @@ public: const QUuid& getWalletID() const { return _walletID; } void setWalletID(const QUuid& walletID); - - QByteArray getUsernameSignature(const QUuid& connectionToken); - bool hasPrivateKey() const { return !_privateKey.isEmpty(); } - void setPrivateKey(const QByteArray& privateKey); qint64 getBalance() const { return _balance; } float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; } @@ -54,6 +50,15 @@ public: void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply); + QByteArray getUsernameSignature(const QUuid& connectionToken); + bool hasPrivateKey() const { return !_privateKey.isEmpty(); } + void setPrivateKey(const QByteArray& privateKey) { _privateKey = privateKey; } + + QByteArray signPlaintext(const QByteArray& plaintext); + + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -70,8 +75,9 @@ private: QString _xmppPassword; QString _discourseApiKey; QUuid _walletID; - qint64 _balance; - bool _hasBalance; + qint64 _balance { 0 }; + bool _hasBalance { false }; + QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain QByteArray _privateKey; }; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index db775983e1..b2eb0cd680 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -92,12 +92,16 @@ void DomainHandler::softReset() { disconnect(); clearSettings(); - + + _connectionDenialsSinceKeypairRegen = 0; + // cancel the failure timeout for any pending requests for settings QMetaObject::invokeMethod(&_settingsTimer, "stop"); } void DomainHandler::hardReset() { + emit resetting(); + softReset(); qCDebug(networking) << "Hard reset in NodeList DomainHandler."; @@ -106,6 +110,9 @@ void DomainHandler::hardReset() { _hostname = QString(); _sockAddr.clear(); + _hasCheckedForAccessToken = false; + _domainConnectionRefusals.clear(); + // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); } @@ -347,3 +354,35 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes emit icePeerSocketsReceived(); } } + +void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { + // Read deny reason from packet + quint16 reasonSize; + message->readPrimitive(&reasonSize); + QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + + // output to the log so the user knows they got a denied connection request + // and check and signal for an access token so that we can make sure they are logged in + qCWarning(networking) << "The domain-server denied a connection request: " << reason; + qCWarning(networking) << "Make sure you are logged in."; + + if (!_domainConnectionRefusals.contains(reason)) { + _domainConnectionRefusals.append(reason); + emit domainConnectionRefused(reason); + } + + auto& accountManager = AccountManager::getInstance(); + + if (!_hasCheckedForAccessToken) { + accountManager.checkAndSignalForAccessToken(); + _hasCheckedForAccessToken = true; + } + + static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + + // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts + if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { + accountManager.generateNewUserKeypair(); + _connectionDenialsSinceKeypairRegen = 0; + } +} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index f60ac2fbe6..03141d8fef 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -92,6 +92,7 @@ public slots: void processICEPingReplyPacket(QSharedPointer message); void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); void processICEResponsePacket(QSharedPointer icePacket); + void processDomainServerConnectionDeniedPacket(QSharedPointer message); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); @@ -104,6 +105,7 @@ signals: // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on void completedSocketDiscovery(); + void resetting(); void connectedToDomain(const QString& hostname); void disconnectedFromDomain(); @@ -113,6 +115,8 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); + void domainConnectionRefused(QString reason); + private: void sendDisconnectPacket(); void hardReset(); @@ -130,6 +134,10 @@ private: QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + + QStringList _domainConnectionRefusals; + bool _hasCheckedForAccessToken { false }; + int _connectionDenialsSinceKeypairRegen { 0 }; }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h index 4ff0d2ecf2..547b754cb5 100644 --- a/libraries/networking/src/FileResourceRequest.h +++ b/libraries/networking/src/FileResourceRequest.h @@ -19,7 +19,7 @@ class FileResourceRequest : public ResourceRequest { Q_OBJECT public: - FileResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + FileResourceRequest(const QUrl& url) : ResourceRequest(url) { } protected: virtual void doSend() override; diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index 34c9c1dad2..11ab436933 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -28,6 +28,25 @@ HTTPResourceRequest::~HTTPResourceRequest() { } } +void HTTPResourceRequest::setupTimer() { + Q_ASSERT(!_sendTimer); + static const int TIMEOUT_MS = 10000; + + _sendTimer = new QTimer(); + connect(this, &QObject::destroyed, _sendTimer, &QTimer::deleteLater); + connect(_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout); + + _sendTimer->setSingleShot(true); + _sendTimer->start(TIMEOUT_MS); +} + +void HTTPResourceRequest::cleanupTimer() { + Q_ASSERT(_sendTimer); + _sendTimer->disconnect(this); + _sendTimer->deleteLater(); + _sendTimer = nullptr; +} + void HTTPResourceRequest::doSend() { QNetworkRequest networkRequest(_url); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -42,18 +61,15 @@ void HTTPResourceRequest::doSend() { connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished); connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress); - connect(&_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout); - static const int TIMEOUT_MS = 10000; - _sendTimer.setSingleShot(true); - _sendTimer.start(TIMEOUT_MS); + setupTimer(); } void HTTPResourceRequest::onRequestFinished() { Q_ASSERT(_state == InProgress); Q_ASSERT(_reply); - _sendTimer.stop(); + cleanupTimer(); switch(_reply->error()) { case QNetworkReply::NoError: @@ -80,7 +96,7 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT Q_ASSERT(_state == InProgress); // We've received data, so reset the timer - _sendTimer.start(); + _sendTimer->start(); emit progress(bytesReceived, bytesTotal); } @@ -91,6 +107,8 @@ void HTTPResourceRequest::onTimeout() { _reply->abort(); _reply->deleteLater(); _reply = nullptr; + + cleanupTimer(); _result = Timeout; _state = Finished; diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h index f0d3bb82d9..cc628d8855 100644 --- a/libraries/networking/src/HTTPResourceRequest.h +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -21,7 +21,7 @@ class HTTPResourceRequest : public ResourceRequest { Q_OBJECT public: - HTTPResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + HTTPResourceRequest(const QUrl& url) : ResourceRequest(url) { } ~HTTPResourceRequest(); protected: @@ -33,7 +33,10 @@ private slots: void onRequestFinished(); private: - QTimer _sendTimer; + void setupTimer(); + void cleanupTimer(); + + QTimer* _sendTimer { nullptr }; QNetworkReply* _reply { nullptr }; }; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a3707d19ba..f236f9d596 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -902,10 +902,6 @@ void LimitedNodeList::updateLocalSockAddr() { } } -void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr) { - sendPacketToIceServer(PacketType::ICEServerHeartbeat, iceServerSockAddr, _sessionUUID); -} - void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID) { sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index fcad23da8f..de110c7e7f 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -143,6 +143,7 @@ public: bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } + const HifiSockAddr& getPublicSockAddr() const { return _publicSockAddr; } const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; } void processKillNode(ReceivedMessage& message); @@ -161,7 +162,6 @@ public: std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); std::unique_ptr constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID); - void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr); void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr); diff --git a/libraries/networking/src/MessagesClient.cpp b/libraries/networking/src/MessagesClient.cpp index 3a03027da0..d8c63c4294 100644 --- a/libraries/networking/src/MessagesClient.cpp +++ b/libraries/networking/src/MessagesClient.cpp @@ -79,20 +79,29 @@ void MessagesClient::handleMessagesPacket(QSharedPointer receiv QString channel, message; QUuid senderID; decodeMessagesPacket(receivedMessage, channel, message, senderID); - emit messageReceived(channel, message, senderID); + emit messageReceived(channel, message, senderID, false); } -void MessagesClient::sendMessage(QString channel, QString message) { +void MessagesClient::sendMessage(QString channel, QString message, bool localOnly) { auto nodeList = DependencyManager::get(); - SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer); - - if (messagesMixer) { + if (localOnly) { QUuid senderID = nodeList->getSessionUUID(); - auto packetList = encodeMessagesPacket(channel, message, senderID); - nodeList->sendPacketList(std::move(packetList), *messagesMixer); + emit messageReceived(channel, message, senderID, true); + } else { + SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer); + + if (messagesMixer) { + QUuid senderID = nodeList->getSessionUUID(); + auto packetList = encodeMessagesPacket(channel, message, senderID); + nodeList->sendPacketList(std::move(packetList), *messagesMixer); + } } } +void MessagesClient::sendLocalMessage(QString channel, QString message) { + sendMessage(channel, message, true); +} + void MessagesClient::subscribe(QString channel) { _subscribedChannels << channel; auto nodeList = DependencyManager::get(); @@ -123,4 +132,4 @@ void MessagesClient::handleNodeActivated(SharedNodePointer node) { subscribe(channel); } } -} \ No newline at end of file +} diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index ff7874fd8e..b624acccb7 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -29,7 +29,8 @@ public: Q_INVOKABLE void init(); - Q_INVOKABLE void sendMessage(QString channel, QString message); + Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); + Q_INVOKABLE void sendLocalMessage(QString channel, QString message); Q_INVOKABLE void subscribe(QString channel); Q_INVOKABLE void unsubscribe(QString channel); @@ -38,7 +39,7 @@ public: signals: - void messageReceived(QString channel, QString message, QUuid senderUUID); + void messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly); private slots: void handleMessagesPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 677a1ad1e6..02bb17e870 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -80,11 +80,16 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // send a ping punch immediately connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer); + auto &accountManager = AccountManager::getInstance(); + + // assume that we may need to send a new DS check in anytime a new keypair is generated + connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); + // clear out NodeList when login is finished - connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset); + connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset); // clear our NodeList when logout is requested - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + connect(&accountManager, &AccountManager::logoutComplete , this, &NodeList::reset); // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); @@ -105,6 +110,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode"); packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket"); + packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket"); packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket"); packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); @@ -265,6 +271,26 @@ void NodeList::sendDomainServerCheckIn() { } + // check if we're missing a keypair we need to verify ourselves with the domain-server + auto& accountManager = AccountManager::getInstance(); + const QUuid& connectionToken = _domainHandler.getConnectionToken(); + + // we assume that we're on the same box as the DS if it has the same local address and + // it didn't present us with a connection token to use for username signature + bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost + || (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull()); + + bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain; + + if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) { + qWarning() << "A keypair is required to present a username signature to the domain-server" + << "but no keypair is present. Waiting for keypair generation to complete."; + accountManager.generateNewUserKeypair(); + + // don't send the check in packet - wait for the keypair first + return; + } + auto domainPacket = NLPacket::create(domainPacketType); QDataStream packetStream(domainPacket.get()); @@ -289,23 +315,15 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); - - // if this is a connect request, and we can present a username signature, send it along - if (!_domainHandler.isConnected() ) { - - DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo(); + + if (!_domainHandler.isConnected()) { + DataServerAccountInfo& accountInfo = accountManager.getAccountInfo(); packetStream << accountInfo.getUsername(); - - // get connection token from the domain-server - const QUuid& connectionToken = _domainHandler.getConnectionToken(); - - if (!connectionToken.isNull()) { - - const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature(connectionToken); - - if (!usernameSignature.isEmpty()) { - packetStream << usernameSignature; - } + + // if this is a connect request, and we can present a username signature, send it along + if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) { + const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken); + packetStream << usernameSignature; } } diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index 53b9b27cc6..a98cf74564 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() { // we can cleanup the RSA struct before we continue on RSA_free(keyPair); - QByteArray publicKeyArray(reinterpret_cast(publicKeyDER), publicKeyLength); - QByteArray privateKeyArray(reinterpret_cast(privateKeyDER), privateKeyLength); + _publicKey = QByteArray { reinterpret_cast(publicKeyDER), publicKeyLength }; + _privateKey = QByteArray { reinterpret_cast(privateKeyDER), privateKeyLength }; // cleanup the publicKeyDER and publicKeyDER data OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); - emit generatedKeypair(publicKeyArray, privateKeyArray); + emit generatedKeypair(); } diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index dd90313625..36f4a9550b 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -12,17 +12,31 @@ #ifndef hifi_RSAKeypairGenerator_h #define hifi_RSAKeypairGenerator_h -#include +#include +#include class RSAKeypairGenerator : public QObject { Q_OBJECT public: RSAKeypairGenerator(QObject* parent = 0); + + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } + + const QByteArray& getPublicKey() const { return _publicKey; } + const QByteArray& getPrivateKey() const { return _privateKey; } + public slots: void generateKeypair(); + signals: void errorGeneratingKeypair(); - void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + void generatedKeypair(); + +private: + QUuid _domainID; + QByteArray _publicKey; + QByteArray _privateKey; }; -#endif // hifi_RSAKeypairGenerator_h \ No newline at end of file +#endif // hifi_RSAKeypairGenerator_h diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index f195011290..1d41266f3a 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -11,12 +11,20 @@ #include "ResourceManager.h" -#include "AssetResourceRequest.h" -#include "FileResourceRequest.h" -#include "HTTPResourceRequest.h" +#include +#include +#include #include + +#include "AssetResourceRequest.h" +#include "FileResourceRequest.h" +#include "HTTPResourceRequest.h" +#include "NetworkAccessManager.h" + + +QThread ResourceManager::_thread; ResourceManager::PrefixMap ResourceManager::_prefixMap; QMutex ResourceManager::_prefixMapLock; @@ -67,18 +75,41 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) { return url; } +void ResourceManager::init() { + _thread.setObjectName("Ressource Manager Thread"); + + auto assetClient = DependencyManager::set(); + assetClient->moveToThread(&_thread); + QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init); + + _thread.start(); +} + +void ResourceManager::cleanup() { + // cleanup the AssetClient thread + DependencyManager::destroy(); + _thread.quit(); + _thread.wait(); +} + ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { auto normalizedURL = normalizeURL(url); auto scheme = normalizedURL.scheme(); + + ResourceRequest* request = nullptr; + if (scheme == URL_SCHEME_FILE) { - return new FileResourceRequest(parent, normalizedURL); + request = new FileResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { - return new HTTPResourceRequest(parent, normalizedURL); + request = new HTTPResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_ATP) { - return new AssetResourceRequest(parent, normalizedURL); + request = new AssetResourceRequest(normalizedURL); + } else { + qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); + return nullptr; } + Q_ASSERT(request); - qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); - - return nullptr; + request->moveToThread(&_thread); + return request; } diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index c010c67f9b..162892abaf 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -29,8 +29,15 @@ public: static void setUrlPrefixOverride(const QString& prefix, const QString& replacement); static QString normalizeURL(const QString& urlString); static QUrl normalizeURL(const QUrl& url); + static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); + + static void init(); + static void cleanup(); + private: + static QThread _thread; + using PrefixMap = std::map; static PrefixMap _prefixMap; diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index c6880636ea..e6402d6b25 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -11,12 +11,15 @@ #include "ResourceRequest.h" -ResourceRequest::ResourceRequest(QObject* parent, const QUrl& url) : - QObject(parent), - _url(url) { -} +#include + +ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } void ResourceRequest::send() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); + return; + } Q_ASSERT(_state == NotStarted); _state = InProgress; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index b229951a49..f940221d9d 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -20,7 +20,7 @@ class ResourceRequest : public QObject { Q_OBJECT public: - ResourceRequest(QObject* parent, const QUrl& url); + ResourceRequest(const QUrl& url); enum State { NotStarted = 0, @@ -38,7 +38,6 @@ public: NotFound }; - void send(); QByteArray getData() { return _data; } State getState() const { return _state; } Result getResult() const { return _result; } @@ -47,8 +46,11 @@ public: void setCacheEnabled(bool value) { _cacheEnabled = value; } +public slots: + void send(); + signals: - void progress(uint64_t bytesReceived, uint64_t bytesTotal); + void progress(qint64 bytesReceived, qint64 bytesTotal); void finished(); protected: diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp index 54342d9cba..000573d241 100644 --- a/libraries/networking/src/udt/BasePacket.cpp +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -58,7 +58,7 @@ BasePacket::BasePacket(qint64 size) { Q_ASSERT(size >= 0 || size < maxPayload); _packetSize = size; - _packet.reset(new char[_packetSize]); + _packet.reset(new char[_packetSize]()); _payloadCapacity = _packetSize; _payloadSize = 0; _payloadStart = _packet.get(); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 3b5b26d05d..e6a15aa6a0 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -87,7 +87,8 @@ SendQueue& Connection::getSendQueue() { // receiver is getting the sequence numbers it expects (given that the connection must still be active) // Lasily create send queue - _sendQueue = SendQueue::create(_parentSocket, _destination, _inactiveSendQueueSequenceNumber); + _sendQueue = SendQueue::create(_parentSocket, _destination); + _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Created SendQueue for connection to" << _destination; @@ -109,10 +110,6 @@ SendQueue& Connection::getSendQueue() { } void Connection::queueInactive() { - // get the current sequence number from the send queue, this is to be re-used if the send - // queue is re-activated for this connection - _inactiveSendQueueSequenceNumber = _sendQueue->getCurrentSequenceNumber(); - // tell our current send queue to go down and reset our ptr to it to null stopSendQueue(); @@ -728,15 +725,28 @@ void Connection::processNAK(std::unique_ptr controlPacket) { } void Connection::processHandshake(std::unique_ptr controlPacket) { + SequenceNumber initialSequenceNumber; + controlPacket->readPrimitive(&initialSequenceNumber); - if (!_hasReceivedHandshake || _isReceivingData) { + if (!_hasReceivedHandshake || initialSequenceNumber != _initialReceiveSequenceNumber) { // server sent us a handshake - we need to assume this means state should be reset // as long as we haven't received a handshake yet or we have and we've received some data + +#ifdef UDT_CONNECTION_DEBUG + if (initialSequenceNumber != _initialReceiveSequenceNumber) { + qCDebug(networking) << "Resetting receive state, received a new initial sequence number in handshake"; + } +#endif resetReceiveState(); + _initialReceiveSequenceNumber = initialSequenceNumber; + _lastReceivedSequenceNumber = initialSequenceNumber - 1; + _lastSentACK = initialSequenceNumber - 1; } // immediately respond with a handshake ACK - static auto handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, 0); + static auto handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, sizeof(SequenceNumber)); + handshakeACK->seek(0); + handshakeACK->writePrimitive(initialSequenceNumber); _parentSocket->writeBasePacket(*handshakeACK, _destination); // indicate that handshake has been received @@ -746,8 +756,11 @@ void Connection::processHandshake(std::unique_ptr controlPacket) void Connection::processHandshakeACK(std::unique_ptr controlPacket) { // if we've decided to clean up the send queue then this handshake ACK should be ignored, it's useless if (_sendQueue) { + SequenceNumber initialSequenceNumber; + controlPacket->readPrimitive(&initialSequenceNumber); + // hand off this handshake ACK to the send queue so it knows it can start sending - getSendQueue().handshakeACK(); + getSendQueue().handshakeACK(initialSequenceNumber); // indicate that handshake ACK was received _hasReceivedHandshakeACK = true; diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index b58b7ec570..bf56a468aa 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -130,7 +130,9 @@ private: bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection bool _isActive { true }; // flag used for inactivity of connection - + + SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer SendQueue on creation, identifies connection during re-connect requests + LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer SequenceNumber _lastReceivedACK; // The last ACK received @@ -140,8 +142,6 @@ private: SequenceNumber _lastSentACK; // The last sent ACK SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2 - SequenceNumber _inactiveSendQueueSequenceNumber { 0 }; - int _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 88fabf1a5a..3ffbb867bf 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -16,6 +16,10 @@ #include #include + +Q_DECLARE_METATYPE(PacketType); +static int packetTypeMetaTypeId = qRegisterMetaType(); + const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack @@ -30,7 +34,7 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken << PacketType::DomainSettingsRequest << PacketType::DomainSettings << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat - << PacketType::ICEPing << PacketType::ICEPingReply + << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode << PacketType::DomainServerRemovedNode; @@ -38,6 +42,8 @@ const QSet RELIABLE_PACKETS = QSet(); PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { + case PacketType::DomainList: + return 18; case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: @@ -45,6 +51,8 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + case PacketType::ICEServerHeartbeat: + return 18; // ICE Server Heartbeat signing default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 0f586018db..7840ecb17e 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -90,7 +90,8 @@ public: DomainServerRemovedNode, MessagesData, MessagesSubscribe, - MessagesUnsubscribe + MessagesUnsubscribe, + ICEServerHeartbeatDenied }; }; diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 18269d1d43..5c6db5adf3 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -12,6 +12,7 @@ #include "SendQueue.h" #include +#include #include #include @@ -53,10 +54,10 @@ private: Mutex2& _mutex2; }; -std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber) { +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber)); + auto queue = std::unique_ptr(new SendQueue(socket, destination)); // Setup queue private thread QThread* thread = new QThread; @@ -75,12 +76,23 @@ std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destin return queue; } -SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber) : +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : _socket(socket), - _destination(dest), - _currentSequenceNumber(currentSequenceNumber) + _destination(dest) { - + + // setup psuedo-random number generation for all instances of SendQueue + static std::random_device rd; + static std::mt19937 generator(rd()); + static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); + + // randomize the intial sequence number + _initialSequenceNumber = SequenceNumber(distribution(generator)); + + // set our member variables from randomized initial number + _currentSequenceNumber = _initialSequenceNumber - 1; + _atomicCurrentSequenceNumber = uint32_t(_currentSequenceNumber); + _lastACKSequenceNumber = uint32_t(_currentSequenceNumber) - 1; } void SendQueue::queuePacket(std::unique_ptr packet) { @@ -190,7 +202,11 @@ void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { // we haven't received a handshake ACK from the client, send another now - static const auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0); + static const auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, sizeof(SequenceNumber)); + + handshakePacket->seek(0); + + handshakePacket->writePrimitive(_initialSequenceNumber); _socket->writeBasePacket(*handshakePacket, _destination); // we wait for the ACK or the re-send interval to expire @@ -199,14 +215,16 @@ void SendQueue::sendHandshake() { } } -void SendQueue::handshakeACK() { - { - std::lock_guard locker { _handshakeMutex }; - _hasReceivedHandshakeACK = true; +void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { + if (initialSequenceNumber == _initialSequenceNumber) { + { + std::lock_guard locker { _handshakeMutex }; + _hasReceivedHandshakeACK = true; + } + + // Notify on the handshake ACK condition + _handshakeACKCondition.notify_one(); } - - // Notify on the handshake ACK condition - _handshakeACKCondition.notify_one(); } SequenceNumber SendQueue::getNextSequenceNumber() { diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index da8f5e6c3f..0390f2ff1f 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -50,8 +50,7 @@ public: Stopped }; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination, - SequenceNumber currentSequenceNumber = SequenceNumber()); + static std::unique_ptr create(Socket* socket, HifiSockAddr destination); void queuePacket(std::unique_ptr packet); void queuePacketList(std::unique_ptr packetList); @@ -72,7 +71,7 @@ public slots: void ack(SequenceNumber ack); void nak(SequenceNumber start, SequenceNumber end); void overrideNAKListFromPacket(ControlPacket& packet); - void handshakeACK(); + void handshakeACK(SequenceNumber initialSequenceNumber); signals: void packetSent(int dataSize, int payloadSize); @@ -84,7 +83,7 @@ private slots: void run(); private: - SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber); + SendQueue(Socket* socket, HifiSockAddr dest); SendQueue(SendQueue& other) = delete; SendQueue(SendQueue&& other) = delete; @@ -106,6 +105,8 @@ private: Socket* _socket { nullptr }; // Socket to send packet on HifiSockAddr _destination; // Destination addr + + SequenceNumber _initialSequenceNumber; // Randomized on SendQueue creation, identifies connection during re-connect requests std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8c0dab98db..6a818a1972 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -272,7 +272,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; if (_numInactiveUpdates > 0) { - const uint8_t MAX_NUM_INACTIVE_UPDATES = 3; + const uint8_t MAX_NUM_INACTIVE_UPDATES = 20; if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) { // clear local ownership (stop sending updates) and let the server clear itself _entity->clearSimulationOwnership(); @@ -282,7 +282,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // until it is removed from the outgoing updates // (which happens when we don't own the simulation and it isn't touching our simulation) const float INACTIVE_UPDATE_PERIOD = 0.5f; - return (dt > INACTIVE_UPDATE_PERIOD); + return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates); } if (!_body->isActive()) { @@ -404,8 +404,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q assert(_entity); assert(entityTreeIsLocked()); - bool active = _body->isActive(); - if (!active) { + if (!_body->isActive()) { // make sure all derivatives are zero glm::vec3 zero(0.0f); _entity->setVelocity(zero); @@ -495,16 +494,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); #endif //def WANT_DEBUG - if (sessionID == _entity->getSimulatorID()) { - // we think we own the simulation - if (!active) { - // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID - // but we remember that we do still own it... and rely on the server to tell us that we don't - properties.clearSimulationOwner(); - _outgoingPriority = ZERO_SIMULATION_PRIORITY; - } - // else the ownership is not changing so we don't bother to pack it - } else { + if (_numInactiveUpdates > 0) { + // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID + // but we remember that we do still own it... and rely on the server to tell us that we don't + properties.clearSimulationOwner(); + _outgoingPriority = ZERO_SIMULATION_PRIORITY; + } else if (sessionID != _entity->getSimulatorID()) { // we don't own the simulation for this entity yet, but we're sending a bid for it properties.setSimulationOwner(sessionID, glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index f138587030..f956e562cd 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -106,11 +106,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } break; } - if (shape && type != SHAPE_TYPE_COMPOUND) { + if (shape) { if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) { // this shape has an offset, which we support by wrapping the true shape // in a btCompoundShape with a local transform - auto compound = new btCompoundShape(); + auto compound = new btCompoundShape(); btTransform trans; trans.setIdentity(); trans.setOrigin(glmToBullet(info.getOffset())); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 06d8709dc5..c9ea223b3f 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -20,6 +20,7 @@ #include "GeometryCache.h" #include "FramebufferCache.h" +#include "TextureCache.h" #include "DeferredLightingEffect.h" #include "debug_deferred_buffer_vert.h" @@ -39,7 +40,7 @@ void DebugDeferredBufferConfig::setMode(int newMode) { } enum Slot { - Diffuse = 0, + Albedo = 0, Normal, Specular, Depth, @@ -52,27 +53,54 @@ enum Slot { -static const std::string DEFAULT_DIFFUSE_SHADER { +static const std::string DEFAULT_ALBEDO_SHADER { "vec4 getFragmentColor() {" - " return vec4(pow(texture(diffuseMap, uv).xyz, vec3(1.0 / 2.2)), 1.0);" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return vec4(pow(frag.diffuse, vec3(1.0 / 2.2)), 1.0);" " }" }; -static const std::string DEFAULT_SPECULAR_SHADER { +static const std::string DEFAULT_METALLIC_SHADER { "vec4 getFragmentColor() {" - " return vec4(texture(specularMap, uv).xyz, 1.0);" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return vec4(vec3(pow(frag.metallic, 1.0 / 2.2)), 1.0);" " }" }; + static const std::string DEFAULT_ROUGHNESS_SHADER { "vec4 getFragmentColor() {" - " return vec4(vec3(texture(specularMap, uv).a), 1.0);" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return vec4(vec3(pow(frag.roughness, 1.0 / 2.2)), 1.0);" " }" }; static const std::string DEFAULT_NORMAL_SHADER { "vec4 getFragmentColor() {" - " return vec4(normalize(texture(normalMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return vec4(vec3(0.5) + (frag.normal * 0.5), 1.0);" " }" }; + +static const std::string DEFAULT_OCCLUSION_SHADER{ + "vec4 getFragmentColor() {" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return vec4(vec3(frag.obscurance), 1.0);" + " }" +}; + +static const std::string DEFAULT_EMISSIVE_SHADER{ + "vec4 getFragmentColor() {" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return (frag.mode != LIGHT_MAPPED ? vec4(pow(frag.emissive, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " }" +}; + +static const std::string DEFAULT_LIGHTMAP_SHADER{ + "vec4 getFragmentColor() {" + " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + " return (frag.mode == LIGHT_MAPPED ? vec4(frag.emissive, 1.0) : vec4(vec3(0.0), 1.0));" + " }" +}; + static const std::string DEFAULT_DEPTH_SHADER { "vec4 getFragmentColor() {" " return vec4(vec3(texture(depthMap, uv).x), 1.0);" @@ -105,7 +133,7 @@ static const std::string DEFAULT_PYRAMID_DEPTH_SHADER { static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(occlusionMap, uv).x), 1.0);" + " return vec4(vec3(texture(obscuranceMap, 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);" " }" @@ -144,16 +172,22 @@ DebugDeferredBuffer::DebugDeferredBuffer() { std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string customFile) { switch (mode) { - case DiffuseMode: - return DEFAULT_DIFFUSE_SHADER; - case SpecularMode: - return DEFAULT_SPECULAR_SHADER; + case AlbedoMode: + return DEFAULT_ALBEDO_SHADER; + case MetallicMode: + return DEFAULT_METALLIC_SHADER; case RoughnessMode: return DEFAULT_ROUGHNESS_SHADER; case NormalMode: return DEFAULT_NORMAL_SHADER; case DepthMode: return DEFAULT_DEPTH_SHADER; + case EmissiveMode: + return DEFAULT_EMISSIVE_SHADER; + case OcclusionMode: + return DEFAULT_OCCLUSION_SHADER; + case LightmapMode: + return DEFAULT_LIGHTMAP_SHADER; case LightingMode: return DEFAULT_LIGHTING_SHADER; case ShadowMode: @@ -206,14 +240,14 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str const auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("diffuseMap", Diffuse)); + slotBindings.insert(gpu::Shader::Binding("albedoMap", Albedo)); slotBindings.insert(gpu::Shader::Binding("normalMap", Normal)); slotBindings.insert(gpu::Shader::Binding("specularMap", Specular)); slotBindings.insert(gpu::Shader::Binding("depthMap", Depth)); + slotBindings.insert(gpu::Shader::Binding("obscuranceMap", AmbientOcclusion)); 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); @@ -247,6 +281,7 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { const auto geometryBuffer = DependencyManager::get(); const auto framebufferCache = DependencyManager::get(); + const auto textureCache = DependencyManager::get(); const auto& lightStage = DependencyManager::get()->getLightStage(); glm::mat4 projMat; @@ -262,14 +297,19 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren batch.setPipeline(getPipeline(_mode, first)); - batch.setResourceTexture(Diffuse, framebufferCache->getDeferredColorTexture()); + batch.setResourceTexture(Albedo, framebufferCache->getDeferredColorTexture()); batch.setResourceTexture(Normal, framebufferCache->getDeferredNormalTexture()); batch.setResourceTexture(Specular, framebufferCache->getDeferredSpecularTexture()); 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()); + if (DependencyManager::get()->isAmbientOcclusionEnabled()) { + batch.setResourceTexture(AmbientOcclusion, framebufferCache->getOcclusionTexture()); + } else { + // need to assign the white texture if ao is off + batch.setResourceTexture(AmbientOcclusion, textureCache->getWhiteTexture()); + } batch.setResourceTexture(AmbientOcclusionBlurred, framebufferCache->getOcclusionBlurredTexture()); const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index b31985d6a3..f00dd58f0e 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -47,11 +47,14 @@ protected: enum Mode : uint8_t { // Use Mode suffix to avoid collisions - DiffuseMode = 0, - SpecularMode, - RoughnessMode, + DepthMode = 0, + AlbedoMode, NormalMode, - DepthMode, + RoughnessMode, + MetallicMode, + EmissiveMode, + OcclusionMode, + LightmapMode, LightingMode, ShadowMode, PyramidDepthMode, diff --git a/libraries/render-utils/src/DeferredBuffer.slh b/libraries/render-utils/src/DeferredBuffer.slh index 3f9aef6dd1..5a3c941ce3 100755 --- a/libraries/render-utils/src/DeferredBuffer.slh +++ b/libraries/render-utils/src/DeferredBuffer.slh @@ -12,8 +12,8 @@ <@def DEFERRED_BUFFER_SLH@> -// the diffuse texture -uniform sampler2D diffuseMap; +// the albedo texture +uniform sampler2D albedoMap; // the normal texture uniform sampler2D normalMap; @@ -34,8 +34,8 @@ uniform sampler2D lightingMap; struct DeferredTransform { mat4 projection; mat4 viewInverse; - - vec4 stereoSide_spareABC; + float stereoSide; + vec3 _spareABC; }; layout(std140) uniform deferredTransformBuffer { @@ -46,10 +46,10 @@ DeferredTransform getDeferredTransform() { } bool getStereoMode(DeferredTransform deferredTransform) { - return (deferredTransform.stereoSide_spareABC.x != 0.0); + return (deferredTransform.stereoSide != 0.0); } float getStereoSide(DeferredTransform deferredTransform) { - return (deferredTransform.stereoSide_spareABC.x); + return (deferredTransform.stereoSide); } vec4 evalEyePositionFromZ(DeferredTransform deferredTransform, float depthVal, vec2 texcoord) { @@ -64,52 +64,82 @@ vec4 evalEyePositionFromZ(DeferredTransform deferredTransform, float depthVal, v } struct DeferredFragment { - float depthVal; vec4 normalVal; vec4 diffuseVal; vec4 specularVal; vec4 position; vec3 normal; + float metallic; vec3 diffuse; float obscurance; vec3 specular; - float gloss; + float roughness; + vec3 emissive; int mode; + float depthVal; }; 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; - +vec4 unpackDeferredPosition(DeferredTransform deferredTransform, float depthValue, vec2 texcoord) { if (getStereoMode(deferredTransform)) { if (texcoord.x > 0.5) { texcoord.x -= 0.5; } texcoord.x *= 2.0; } - frag.position = evalEyePositionFromZ(deferredTransform, frag.depthVal, texcoord); + return evalEyePositionFromZ(deferredTransform, depthValue, texcoord); +} + +DeferredFragment unpackDeferredFragmentNoPosition(vec2 texcoord) { + + DeferredFragment frag; + frag.depthVal = -1; + frag.normalVal = texture(normalMap, texcoord); + frag.diffuseVal = texture(albedoMap, texcoord); + frag.specularVal = texture(specularMap, texcoord); + frag.obscurance = texture(obscuranceMap, texcoord).x; // 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.emissive = frag.specularVal.xyz; + if (frag.normalVal.a < 0.5) { + frag.mode = 0; + frag.roughness = 2.0 * frag.normalVal.a; + } else { frag.mode = LIGHT_MAPPED; + frag.roughness = 2.0 * frag.normalVal.a - 1.0; } + frag.metallic = frag.diffuseVal.a; frag.diffuse = frag.diffuseVal.xyz; - frag.specular = frag.specularVal.xyz; - frag.gloss = frag.specularVal.w; + if (frag.metallic <= 0.5) { + frag.metallic = 0.0; + frag.specular = vec3(0.03); // Default Di-electric fresnel value + } else { + frag.specular = vec3(frag.diffuseVal.xyz); + frag.metallic = 1.0; + } + frag.obscurance = min(frag.specularVal.w, frag.obscurance); + return frag; +} + +DeferredFragment unpackDeferredFragment(DeferredTransform deferredTransform, vec2 texcoord) { + + float depthValue = texture(depthMap, texcoord).r; + + DeferredFragment frag = unpackDeferredFragmentNoPosition(texcoord); + + frag.depthVal = depthValue; + frag.position = unpackDeferredPosition(deferredTransform, frag.depthVal, texcoord); return frag; } + + <@endif@> diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index 1045c4afc7..2a2c2883d3 100755 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -40,37 +40,41 @@ float evalOpaqueFinalAlpha(float alpha, float mapAlpha) { return mix(alpha, 1.0 - alpha, step(mapAlpha, alphaThreshold)); } -const vec3 DEFAULT_SPECULAR = vec3(0.1); +const float DEFAULT_ROUGHNESS = 0.9; const float DEFAULT_SHININESS = 10; +const float DEFAULT_METALLIC = 0; +const vec3 DEFAULT_SPECULAR = vec3(0.1); +const vec3 DEFAULT_EMISSIVE = vec3(0.0); +const float DEFAULT_OCCLUSION = 1.0; -void packDeferredFragment(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { + +void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion) { if (alpha != 1.0) { discard; } - - _fragColor0 = vec4(diffuse.rgb, 1.0); // Opaque - _fragColor1 = vec4(bestFitNormal(normal), 1.0); - _fragColor2 = vec4(specular, shininess / 128.0); + _fragColor0 = vec4(albedo, metallic); + _fragColor1 = vec4(bestFitNormal(normal), 0.5 * clamp(roughness, 0.0, 1.0)); + _fragColor2 = vec4(emissive, occlusion); } -void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess, vec3 emissive) { + +void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 fresnel, vec3 emissive) { if (alpha != 1.0) { discard; } - - _fragColor0 = vec4(diffuse.rgb, 0.5); - _fragColor1 = vec4(bestFitNormal(normal), 0.5); - _fragColor2 = vec4(emissive, shininess / 128.0); + _fragColor0 = vec4(albedo, metallic); + _fragColor1 = vec4(bestFitNormal(normal), 0.5 + 0.5 * clamp(roughness, 0.0, 1.0)); + _fragColor2 = vec4(emissive, 1.0); } -void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { +void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 albedo, vec3 fresnel, float roughness) { if (alpha <= 0.0) { discard; } - _fragColor0 = vec4(diffuse.rgb, alpha); + _fragColor0 = vec4(albedo.rgb, alpha); // _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - // _fragColor2 = vec4(specular, shininess / 128.0); + // _fragColor2 = vec4(fresnel, roughness); } <@endif@> diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index 71c533de24..71bc2dc6d0 100755 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -11,109 +11,116 @@ <@if not DEFERRED_GLOBAL_LIGHT_SLH@> <@def DEFERRED_GLOBAL_LIGHT_SLH@> +<@include model/Light.slh@> <@include DeferredLighting.slh@> <@func declareSkyboxMap()@> - +// declareSkyboxMap uniform samplerCube skyboxMap; vec4 evalSkyboxLight(vec3 direction, float lod) { - // FIXME - //vec4 skytexel = textureLod(skyboxMap, direction, lod * textureQueryLevels(skyboxMap)); - vec4 skytexel = texture(skyboxMap, direction); - return skytexel; + // textureQueryLevels is not available until #430, so we require explicit lod + // float mipmapLevel = lod * textureQueryLevels(skyboxMap); + return textureLod(skyboxMap, direction, lod); } - <@endfunc@> -// Everything about light -<@include model/Light.slh@> +<@func prepareGlobalLight()@> + // prepareGlobalLight + + // Transform directions to worldspace + vec3 fragNormal = vec3(invViewMat * vec4(normal, 0.0)); + vec3 fragEyeVector = vec3(invViewMat * vec4(-position, 0.0)); + vec3 fragEyeDir = normalize(fragEyeVector); + + // Get light + Light light = getLight(); + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic > 0.5) { + fresnel = albedo; + metallic = 1.0; + } + vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, metallic, fresnel, roughness); + vec3 color = vec3(albedo * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); + color += emissive; +<@endfunc@> + +<@func declareAmbientFresnel()@> +vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) { + return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); +} +<@endfunc@> <@func declareEvalAmbientGlobalColor()@> -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(); - - vec3 fragNormal = vec3(invViewMat * vec4(normal, 0.0)); - vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); - vec3 fragEyeDir = normalize(fragEyeVector.xyz); - - 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) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); - +vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { + <$prepareGlobalLight()$> + color += albedo * getLightColor(light) * obscurance * getLightAmbientIntensity(light); return color; } <@endfunc@> <@func declareEvalAmbientSphereGlobalColor()@> +<$declareAmbientFresnel()$> -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(); - - vec3 fragNormal = normalize(vec3(invViewMat * vec4(normal, 0.0))); - vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); - vec3 fragEyeDir = normalize(fragEyeVector.xyz); - - vec3 ambientNormal = fragNormal.xyz; - vec3 color = diffuse.rgb * evalSphericalLight(getLightAmbientSphere(light), ambientNormal).xyz * obscurance * getLightAmbientIntensity(light); +vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { + <$prepareGlobalLight()$> - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); + // Diffuse from ambient + color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); - color += vec3(diffuse * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); + // Specular highlight from ambient + vec3 direction = -reflect(fragEyeDir, fragNormal); + vec3 skyboxLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz; + vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); + color += ambientFresnel * skyboxLight.rgb * obscurance * getLightAmbientIntensity(light); return color; } <@endfunc@> <@func declareEvalSkyboxGlobalColor()@> - <$declareSkyboxMap()$> +<$declareAmbientFresnel()$> -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(); - - vec3 fragNormal = normalize(vec3(invViewMat * vec4(normal, 0.0))); - vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); - vec3 fragEyeDir = normalize(fragEyeVector.xyz); - - vec3 color = diffuse.rgb * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); +vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { + <$prepareGlobalLight()$> - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); + // Diffuse from ambient + color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); - color += vec3(diffuse * shading.w + shading.rgb) * min(shadowAttenuation, obscurance) * getLightColor(light) * getLightIntensity(light); + // Specular highlight from ambient + vec3 direction = -reflect(fragEyeDir, fragNormal); + float levels = getLightAmbientMapNumMips(light); + float lod = min(floor((roughness) * levels), levels); + vec4 skyboxLight = evalSkyboxLight(direction, lod); + vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); + color += ambientFresnel * skyboxLight.rgb * obscurance * getLightAmbientIntensity(light); return color; } <@endfunc@> <@func declareEvalLightmappedColor()@> -vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 diffuse, vec3 lightmap) { - +vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 albedo, vec3 lightmap) { Light light = getLight(); - vec3 fragNormal = vec3(invViewMat * vec4(normal, 0.0)); - float diffuseDot = dot(fragNormal, -getLightDirection(light)); - - // need to catch normals perpendicular to the projection plane hence the magic number for the threshold - // it should be just 0, but we have innacurracy so we need to overshoot + // Catch normals perpendicular to the projection plane, hence the magic number for the threshold + // It should be just 0, but we have inaccuracy so we overshoot const float PERPENDICULAR_THRESHOLD = -0.005; + vec3 fragNormal = vec3(invViewMat * vec4(normal, 0.0)); // transform to worldspace + float diffuseDot = dot(fragNormal, -getLightDirection(light)); float facingLight = step(PERPENDICULAR_THRESHOLD, diffuseDot); - //float facingLight = step(PERPENDICULAR_THRESHOLD, diffuseDot); - // evaluate the shadow test but only relevant for light facing fragments + + // Reevaluate the shadow attenuation for light facing fragments float lightAttenuation = (1 - facingLight) + facingLight * shadowAttenuation; - // diffuse light is the lightmap dimmed by shadow + + // Diffuse light is the lightmap dimmed by shadow vec3 diffuseLight = lightAttenuation * lightmap; - // ambient is a tiny percentage of the lightmap and only when in the shadow + // Ambient light is the lightmap when in shadow vec3 ambientLight = (1 - lightAttenuation) * lightmap * getLightAmbientIntensity(light); - return obscurance * diffuse * (ambientLight + diffuseLight); + return obscurance * albedo * (diffuseLight + ambientLight); } <@endfunc@> diff --git a/libraries/render-utils/src/DeferredLighting.slh b/libraries/render-utils/src/DeferredLighting.slh index cf03861c2f..2e06deb0af 100755 --- a/libraries/render-utils/src/DeferredLighting.slh +++ b/libraries/render-utils/src/DeferredLighting.slh @@ -13,41 +13,55 @@ <@func declareEvalPBRShading()@> + +vec3 fresnelSchlick(vec3 fresnelColor, vec3 lightDir, vec3 halfDir) { + return fresnelColor + (1.0 - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); +} + +float specularDistribution(float roughness, vec3 normal, vec3 halfDir) { + float ndoth = clamp(dot(halfDir, normal), 0.0, 1.0); + float gloss2 = pow(0.001 + roughness, 4); + float denom = (ndoth * ndoth*(gloss2 - 1) + 1); + float power = gloss2 / (3.14159 * denom * denom); + return power; +} + // Frag Shading returns the diffuse amount as W and the specular rgb as xyz -vec4 evalPBRShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, vec3 specular, float gloss) { +vec4 evalPBRShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 fresnel, float roughness) { // Diffuse Lighting - float diffuseDot = dot(fragNormal, fragLightDir); - float facingLight = step(0.0, diffuseDot); - float diffuse = diffuseDot * facingLight; - - // Specular Lighting depends on the half vector and the gloss + float diffuse = clamp(dot(fragNormal, fragLightDir), 0.0, 1.0); + + // Specular Lighting vec3 halfDir = normalize(fragEyeDir + fragLightDir); + vec3 fresnelColor = fresnelSchlick(fresnel, fragLightDir,halfDir); + float power = specularDistribution(roughness, fragNormal, halfDir); + vec3 specular = power * fresnelColor * diffuse; - float specularPower = pow(max(0.0, dot(halfDir, fragNormal)), gloss * 128.0); - specularPower *= (gloss * 128.0 * 0.125 + 0.25); - - float shlickPower = (1.0 - dot(fragLightDir,halfDir)); - float shlickPower2 = shlickPower * shlickPower; - float shlickPower5 = shlickPower2 * shlickPower2 * shlickPower; - vec3 fresnel = specular * (1.0 - shlickPower5) + vec3(shlickPower5); - vec3 reflect = specularPower * fresnel * diffuse; - - return vec4(reflect, diffuse * (1 - fresnel.x)); + return vec4(specular, (1.0 - metallic) * diffuse * (1 - fresnelColor.x)); } <@endfunc@> <@func declareEvalBlinnRShading()@> -vec4 evalBlinnShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, vec3 specular, float gloss) { +vec4 evalBlinnShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, vec3 specular, float roughness) { // Diffuse Lighting float diffuseDot = dot(fragNormal, fragLightDir); float facingLight = step(0.0, diffuseDot); float diffuse = diffuseDot * facingLight; - // Specular Lighting depends on the half vector and the gloss + // Specular Lighting depends on the half vector and the roughness vec3 halfDir = normalize(fragEyeDir + fragLightDir); - float specularPower = pow(facingLight * max(0.0, dot(halfDir, fragNormal)), gloss * 128.0); + float gloss = (1.0 - roughness) * 128.0; + glos *= gloss; + float specularPower = pow(facingLight * max(0.0, dot(halfDir, fragNormal)), gloss); vec3 reflect = specularPower * specular * diffuse; return vec4(reflect, diffuse); @@ -59,8 +73,8 @@ vec4 evalBlinnShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, vec3 <$declareEvalPBRShading()$> // Return xyz the specular/reflection component and w the diffuse component -vec4 evalFragShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, vec3 specular, float gloss) { - return evalPBRShading(fragNormal, fragLightDir, fragEyeDir, specular, gloss); +vec4 evalFragShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 specular, float roughness) { + return evalPBRShading(fragNormal, fragLightDir, fragEyeDir, metallic, specular, roughness); } <@endif@> diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index c1c7240f20..82bd0e73d0 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -92,6 +92,11 @@ void DeferredLightingEffect::init() { // Add the global light to the light stage (for later shadow rendering) _lightStage.addLight(lp); + lp->setDirection(glm::vec3(-1.0f)); + lp->setColor(glm::vec3(1.0f)); + lp->setIntensity(1.0f); + lp->setType(model::Light::SUN); + lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE); } void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color, @@ -311,22 +316,23 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo { auto& program = _shadowMapEnabled ? _directionalLightShadow : _directionalLight; LightLocationsPtr locations = _shadowMapEnabled ? _directionalLightShadowLocations : _directionalLightLocations; + const auto& keyLight = _allocatedLights[_globalLights.front()]; // Setup the global directional pass pipeline { if (_shadowMapEnabled) { - if (_skyboxTexture) { + if (keyLight->getAmbientMap()) { program = _directionalSkyboxLightShadow; locations = _directionalSkyboxLightShadowLocations; - } else if (_ambientLightMode > -1) { + } else { program = _directionalAmbientSphereLightShadow; locations = _directionalAmbientSphereLightShadowLocations; } } else { - if (_skyboxTexture) { + if (keyLight->getAmbientMap()) { program = _directionalSkyboxLight; locations = _directionalSkyboxLightLocations; - } else if (_ambientLightMode > -1) { + } else { program = _directionalAmbientSphereLight; locations = _directionalAmbientSphereLightLocations; } @@ -351,7 +357,7 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo geometryCache->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); } - if (_skyboxTexture) { + if (keyLight->getAmbientMap()) { batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr); } } @@ -490,14 +496,14 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int skyboxCubemapUnit) { PerformanceTimer perfTimer("DLE->setupBatch()"); - auto globalLight = _allocatedLights[_globalLights.front()]; + auto keyLight = _allocatedLights[_globalLights.front()]; if (lightBufferUnit >= 0) { - batch.setUniformBuffer(lightBufferUnit, globalLight->getSchemaBuffer()); + batch.setUniformBuffer(lightBufferUnit, keyLight->getSchemaBuffer()); } - if (_skyboxTexture && (skyboxCubemapUnit >= 0)) { - batch.setResourceTexture(skyboxCubemapUnit, _skyboxTexture); + if (keyLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) { + batch.setResourceTexture(skyboxCubemapUnit, keyLight->getAmbientMap()); } } @@ -508,7 +514,7 @@ 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"), DEFERRED_BUFFER_COLOR_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), 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)); @@ -556,15 +562,14 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo } -void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light, const gpu::TexturePointer& skyboxTexture) { +void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light) { auto globalLight = _allocatedLights.front(); globalLight->setDirection(light->getDirection()); globalLight->setColor(light->getColor()); globalLight->setIntensity(light->getIntensity()); globalLight->setAmbientIntensity(light->getAmbientIntensity()); globalLight->setAmbientSphere(light->getAmbientSphere()); - - _skyboxTexture = skyboxTexture; + globalLight->setAmbientMap(light->getAmbientMap()); } model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 75cefc277d..63d8f4d175 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -49,11 +49,12 @@ public: void setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int skyboxCubemapUnit); // update global lighting - void setGlobalLight(const model::LightPointer& light, const gpu::TexturePointer& skyboxTexture); + void setGlobalLight(const model::LightPointer& light); const LightStage& getLightStage() { return _lightStage; } void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; }; void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; } + bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; } private: DeferredLightingEffect() = default; @@ -95,15 +96,12 @@ private: std::vector _pointLights; std::vector _spotLights; - int _ambientLightMode = 0; - gpu::TexturePointer _skyboxTexture; - // Class describing the uniform buffer with all the parameters common to the deferred shaders class DeferredTransform { public: glm::mat4 projection; glm::mat4 viewInverse; - float stereoSide{ 0.f }; + float stereoSide { 0.f }; float spareA, spareB, spareC; DeferredTransform() {} diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 756e18dc8b..82a92f572e 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -506,7 +506,7 @@ GeometryCache::GeometryCache() : std::make_shared(getSimplePipeline(), nullptr, [](const render::ShapePipeline&, gpu::Batch& batch) { // Set the defaults needed for a simple program - batch.setResourceTexture(render::ShapePipeline::Slot::DIFFUSE_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP, DependencyManager::get()->getWhiteTexture()); batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP, DependencyManager::get()->getNormalFittingTexture()); @@ -1734,9 +1734,9 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) { void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool culled, bool emissive, bool depthBiased) { batch.setPipeline(getSimplePipeline(textured, culled, emissive, depthBiased)); - // If not textured, set a default diffuse map + // If not textured, set a default albedo map if (!textured) { - batch.setResourceTexture(render::ShapePipeline::Slot::DIFFUSE_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP, DependencyManager::get()->getWhiteTexture()); } // Set a default normal map diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh new file mode 100644 index 0000000000..8904ae34b2 --- /dev/null +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -0,0 +1,139 @@ + +<@if not MODEL_MATERIAL_TEXTURES_SLH@> +<@def MODEL_MATERIAL_TEXTURES_SLH@> + +<@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion)@> + +<@if withAlbedo@> +uniform sampler2D albedoMap; +vec4 fetchAlbedoMap(vec2 uv) { + return texture(albedoMap, uv); +} +<@endif@> + +<@if withRoughness@> +uniform sampler2D roughnessMap; +float fetchRoughnessMap(vec2 uv) { + return (texture(roughnessMap, uv).r); +} +<@endif@> + +<@if withNormal@> +uniform sampler2D normalMap; +vec3 fetchNormalMap(vec2 uv) { + return texture(normalMap, uv).xyz; +} +<@endif@> + +<@if withMetallic@> +uniform sampler2D metallicMap; +float fetchMetallicMap(vec2 uv) { + return (texture(metallicMap, uv).r); +} +<@endif@> + +<@if withEmissive@> +uniform sampler2D emissiveMap; +vec3 fetchEmissiveMap(vec2 uv) { + return texture(emissiveMap, uv).rgb; +} +<@endif@> + +<@if withOcclusion@> +uniform sampler2D occlusionMap; +float fetchOcclusionMap(vec2 uv) { + return texture(occlusionMap, uv).r; +} +<@endif@> +<@endfunc@> + + +<@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@> +<@if albedo@> + vec4 <$albedo$> = (((<$matKey$> & ALBEDO_MAP_BIT) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0)); +<@endif@> +<@if roughness@> + float <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? fetchRoughnessMap(<$texcoord0$>) : 1.0); +<@endif@> +<@if normal@> + vec3 <$normal$> = (((<$matKey$> & NORMAL_MAP_BIT) != 0) ? fetchNormalMap(<$texcoord0$>) : vec3(0.0, 1.0, 0.0)); +<@endif@> +<@if metallic@> + float <$metallic$> = (((<$matKey$> & METALLIC_MAP_BIT) != 0) ? fetchMetallicMap(<$texcoord0$>) : 0.0); +<@endif@> +<@if emissive@> + vec3 <$emissive$> = (((<$matKey$> & EMISSIVE_MAP_BIT) != 0) ? fetchEmissiveMap(<$texcoord0$>) : vec3(0.0)); +<@endif@> +<@if occlusion@> + float <$occlusion$> = (((<$matKey$> & OCCLUSION_MAP_BIT) != 0) ? fetchOcclusionMap(<$texcoord0$>) : 1.0); +<@endif@> +<@endfunc@> + + +<@func declareMaterialLightmap()@> +uniform sampler2D emissiveMap; +uniform vec2 emissiveParams; +vec3 fetchLightmapMap(vec2 uv) { + return (vec3(emissiveParams.x) + emissiveParams.y * texture(emissiveMap, uv).rgb); +} +<@endfunc@> + +<@func fetchMaterialLightmap(texcoord1, lightmapVal)@> + vec3 <$lightmapVal$> = fetchLightmapMap(<$texcoord1$>); +<@endfunc@> + + +<@func tangentToViewSpace(fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> +{ + vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz); + vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz); + vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 localNormal = normalize(<$fetchedNormal$> - vec3(0.5, 0.5, 0.5)); + <$normal$> = vec3(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z); +} +<@endfunc@> + +<@func evalMaterialAlbedo(fetchedAlbedo, materialAlbedo, matKey, albedo)@> +{ + <$albedo$>.xyz = (((<$matKey$> & ALBEDO_VAL_BIT) != 0) ? <$materialAlbedo$> : vec3(1.0)); + + if (((<$matKey$> & ALBEDO_MAP_BIT) != 0)) { + <$albedo$>.xyz *= <$fetchedAlbedo$>.xyz; + } +} +<@endfunc@> + +<@func evalMaterialRoughness(fetchedRoughness, materialRoughness, matKey, roughness)@> +{ + <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? <$fetchedRoughness$> : <$materialRoughness$>); +} +<@endfunc@> + +<@func evalMaterialMetallic(fetchedMetallic, materialMetallic, matKey, metallic)@> +{ + <$metallic$> = (((<$matKey$> & METALLIC_MAP_BIT) != 0) ? <$fetchedMetallic$> : <$materialMetallic$>); +} +<@endfunc@> + +<@func evalMaterialEmissive(fetchedEmissive, materialEmissive, matKey, emissive)@> +{ + <$emissive$> = (((<$matKey$> & EMISSIVE_MAP_BIT) != 0) ? <$fetchedEmissive$> : <$materialEmissive$>); +} +<@endfunc@> + +<@func evalMaterialOcclusion(fetchedOcclusion, matKey, occlusion)@> +{ + <$occlusion$> = <$fetchedOcclusion$>; +} +<@endfunc@> + +<@endif@> \ No newline at end of file diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index e5a313e3b1..166b14333f 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -106,7 +106,7 @@ ShapeKey MeshPartPayload::getShapeKey() const { if (drawMaterialKey.isNormalMap()) { builder.withTangents(); } - if (drawMaterialKey.isGlossMap()) { + if (drawMaterialKey.isMetallicMap()) { builder.withSpecular(); } if (drawMaterialKey.isLightmapMap()) { @@ -145,20 +145,34 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat auto textureMaps = _drawMaterial->getTextureMaps(); glm::mat4 texcoordTransform[2]; - // Diffuse - if (materialKey.isDiffuseMap()) { - auto diffuseMap = textureMaps[model::MaterialKey::DIFFUSE_MAP]; - if (diffuseMap && diffuseMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::DIFFUSE_MAP, diffuseMap->getTextureView()); + // Albedo + if (materialKey.isAlbedoMap()) { + auto albedoMap = textureMaps[model::MaterialKey::ALBEDO_MAP]; + if (albedoMap && albedoMap->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, albedoMap->getTextureView()); - if (!diffuseMap->getTextureTransform().isIdentity()) { - diffuseMap->getTextureTransform().getMatrix(texcoordTransform[0]); + if (!albedoMap->getTextureTransform().isIdentity()) { + albedoMap->getTextureTransform().getMatrix(texcoordTransform[0]); } } else { - batch.setResourceTexture(ShapePipeline::Slot::DIFFUSE_MAP, textureCache->getGrayTexture()); + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, textureCache->getGrayTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::DIFFUSE_MAP, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, textureCache->getWhiteTexture()); + } + + // Roughness map + if (materialKey.isRoughnessMap()) { + auto roughnessMap = textureMaps[model::MaterialKey::ROUGHNESS_MAP]; + if (roughnessMap && roughnessMap->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, roughnessMap->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, textureCache->getWhiteTexture()); + } + } else { + batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, textureCache->getWhiteTexture()); } // Normal map @@ -167,7 +181,7 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat if (normalMap && normalMap->isDefined()) { batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, normalMap->getTextureView()); - // texcoord are assumed to be the same has diffuse + // texcoord are assumed to be the same has albedo } else { batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, textureCache->getBlueTexture()); } @@ -175,26 +189,40 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, nullptr); } - // TODO: For now gloss map is used as the "specular map in the shading, we ll need to fix that - if (materialKey.isGlossMap()) { - auto specularMap = textureMaps[model::MaterialKey::GLOSS_MAP]; + // Metallic map + if (materialKey.isMetallicMap()) { + auto specularMap = textureMaps[model::MaterialKey::METALLIC_MAP]; if (specularMap && specularMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::SPECULAR_MAP, specularMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, specularMap->getTextureView()); - // texcoord are assumed to be the same has diffuse + // texcoord are assumed to be the same has albedo } else { - batch.setResourceTexture(ShapePipeline::Slot::SPECULAR_MAP, textureCache->getBlackTexture()); + batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, textureCache->getBlackTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::SPECULAR_MAP, nullptr); + batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, nullptr); } - // TODO: For now lightmaop is piped into the emissive map unit, we need to fix that and support for real emissive too + // Occlusion map + if (materialKey.isOcclusionMap()) { + auto specularMap = textureMaps[model::MaterialKey::OCCLUSION_MAP]; + if (specularMap && specularMap->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, specularMap->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, textureCache->getWhiteTexture()); + } + } else { + batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, nullptr); + } + + // Emissive / Lightmap if (materialKey.isLightmapMap()) { auto lightmapMap = textureMaps[model::MaterialKey::LIGHTMAP_MAP]; if (lightmapMap && lightmapMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::LIGHTMAP_MAP, lightmapMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, lightmapMap->getTextureView()); auto lightmapOffsetScale = lightmapMap->getLightmapOffsetScale(); batch._glUniform2f(locations->emissiveParams, lightmapOffsetScale.x, lightmapOffsetScale.y); @@ -203,10 +231,18 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat lightmapMap->getTextureTransform().getMatrix(texcoordTransform[1]); } } else { - batch.setResourceTexture(ShapePipeline::Slot::LIGHTMAP_MAP, textureCache->getGrayTexture()); + batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, textureCache->getGrayTexture()); + } + } else if (materialKey.isEmissiveMap()) { + auto emissiveMap = textureMaps[model::MaterialKey::EMISSIVE_MAP]; + + if (emissiveMap && emissiveMap->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, emissiveMap->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, textureCache->getBlackTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::LIGHTMAP_MAP, nullptr); + batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, nullptr); } // Texcoord transforms ? @@ -378,7 +414,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { bool isTranslucent = drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentMap(); bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty(); - bool hasSpecular = drawMaterialKey.isGlossMap(); + bool hasSpecular = drawMaterialKey.isMetallicMap(); bool hasLightmap = drawMaterialKey.isLightmapMap(); bool isSkinned = _isSkinned; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c29c37140e..ce6dfd6849 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1205,9 +1205,9 @@ void Model::segregateMeshGroups() { if (showingCollisionHull) { if (!_collisionHullMaterial) { _collisionHullMaterial = std::make_shared(); - _collisionHullMaterial->setDiffuse(glm::vec3(1.0f, 0.5f, 0.0f)); + _collisionHullMaterial->setAlbedo(glm::vec3(1.0f, 0.5f, 0.0f)); _collisionHullMaterial->setMetallic(0.02f); - _collisionHullMaterial->setGloss(1.0f); + _collisionHullMaterial->setRoughness(0.5f); } _renderItemsSet << std::make_shared(networkMesh._mesh, partIndex, _collisionHullMaterial, transform, offset); } else { diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 0954c7ef83..aa0f9dc7d9 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -66,16 +66,16 @@ void initStencilPipeline(gpu::PipelinePointer& pipeline) { gpu::BufferView getDefaultMaterialBuffer() { model::Material::Schema schema; - schema._diffuse = vec3(1.0f); + schema._albedo = vec3(1.0f); schema._opacity = 1.0f; - schema._metallic = vec3(0.1f); - schema._gloss = 10.0f; + schema._metallic = 0.1f; + schema._roughness = 0.9f; return gpu::BufferView(std::make_shared(sizeof(model::Material::Schema), (const gpu::Byte*) &schema)); } void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch) { - // Set a default diffuse map - batch.setResourceTexture(render::ShapePipeline::Slot::DIFFUSE_MAP, + // Set a default albedo map + batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP, DependencyManager::get()->getWhiteTexture()); // Set a default normal map batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP, diff --git a/libraries/render-utils/src/animdebugdraw.slv b/libraries/render-utils/src/animdebugdraw.slv index 3cb356c055..ffa44b6cee 100644 --- a/libraries/render-utils/src/animdebugdraw.slv +++ b/libraries/render-utils/src/animdebugdraw.slv @@ -16,7 +16,7 @@ out vec4 _color; void main(void) { - // pass along the diffuse color + // pass along the color _color = colorToLinearRGBA(inColor.rgba); TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/directional_ambient_light.slf b/libraries/render-utils/src/directional_ambient_light.slf index 3d1b9db46c..e1ec0d3ef0 100755 --- a/libraries/render-utils/src/directional_ambient_light.slf +++ b/libraries/render-utils/src/directional_ambient_light.slf @@ -44,8 +44,9 @@ void main(void) { frag.position.xyz, frag.normal, frag.diffuse, - frag.specular, - frag.gloss); + frag.metallic, + frag.emissive, + frag.roughness); _fragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index ffe0e21851..963a10d579 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -46,8 +46,9 @@ void main(void) { frag.position.xyz, frag.normal, frag.diffuse, - frag.specular, - frag.gloss); + frag.metallic, + frag.emissive, + frag.roughness); _fragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_light.slf b/libraries/render-utils/src/directional_light.slf index e07c57a905..1a739f9389 100644 --- a/libraries/render-utils/src/directional_light.slf +++ b/libraries/render-utils/src/directional_light.slf @@ -45,8 +45,9 @@ void main(void) { frag.position.xyz, frag.normal, frag.diffuse, - frag.specular, - frag.gloss); + frag.metallic, + frag.emissive, + frag.roughness); _fragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_light_shadow.slf b/libraries/render-utils/src/directional_light_shadow.slf index 4aa041b847..3b9b4d22c6 100644 --- a/libraries/render-utils/src/directional_light_shadow.slf +++ b/libraries/render-utils/src/directional_light_shadow.slf @@ -47,8 +47,9 @@ void main(void) { frag.position.xyz, frag.normal, frag.diffuse, - frag.specular, - frag.gloss); + frag.metallic, + frag.emissive, + frag.roughness); _fragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_skybox_light.slf b/libraries/render-utils/src/directional_skybox_light.slf index 78fdc4e234..9e24a5f585 100755 --- a/libraries/render-utils/src/directional_skybox_light.slf +++ b/libraries/render-utils/src/directional_skybox_light.slf @@ -45,8 +45,9 @@ void main(void) { frag.position.xyz, frag.normal, frag.diffuse, - frag.specular, - frag.gloss); + frag.metallic, + frag.emissive, + frag.roughness); _fragColor = vec4(color, frag.normalVal.a); } diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index bbce15be68..c3008b5509 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -47,8 +47,9 @@ void main(void) { frag.position.xyz, frag.normal, frag.diffuse, - frag.specular, - frag.gloss); + frag.metallic, + frag.emissive, + frag.roughness); _fragColor = vec4(color, frag.normalVal.a); } diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index f455030f6f..c9ca6d9eb7 100755 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -15,8 +15,8 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> in vec4 _position; in vec3 _normal; @@ -25,15 +25,26 @@ in vec2 _texCoord0; void main(void) { - // Fetch diffuse map - vec4 diffuse = texture(diffuseMap, _texCoord0); - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; packDeferredFragment( normalize(_normal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - getMaterialSpecular(mat), - getMaterialShininess(mat)); + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + albedo, + roughness, + getMaterialMetallic(mat), + emissive, + occlusionTex); } diff --git a/libraries/render-utils/src/model_emissive.slf b/libraries/render-utils/src/model_emissive.slf index 25239691cd..471e613eb4 100644 --- a/libraries/render-utils/src/model_emissive.slf +++ b/libraries/render-utils/src/model_emissive.slf @@ -15,7 +15,7 @@ <@include DeferredBufferWrite.slh@> <@include model/Material.slh@> -uniform sampler2D diffuseMap; +uniform sampler2D albedoMap; in vec2 _texCoord0; in vec3 _normal; @@ -23,16 +23,17 @@ in vec3 _color; in float _alpha; void main(void) { - vec4 texel = texture(diffuseMap, _texCoord0); + vec4 texel = texture(albedoMap, _texCoord0); Material mat = getMaterial(); - vec3 fragColor = getMaterialDiffuse(mat) * texel.rgb * _color; + vec3 fragColor = getMaterialAlbedo(mat) * texel.rgb * _color; packDeferredFragmentLightmap( normalize(_normal), texel.a, vec3(1.0), - getMaterialSpecular(mat), - getMaterialShininess(mat), + getMaterialRoughness(mat), + getMaterialMetallic(mat), + getMaterialFresnel(mat), fragColor); } diff --git a/libraries/render-utils/src/model_lightmap.slf b/libraries/render-utils/src/model_lightmap.slf index ab92d2bf4c..46d81d39e1 100755 --- a/libraries/render-utils/src/model_lightmap.slf +++ b/libraries/render-utils/src/model_lightmap.slf @@ -16,12 +16,9 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the emissive map texture and parameters -uniform sampler2D emissiveMap; -uniform vec2 emissiveParams; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS)$> +<$declareMaterialLightmap()$> in vec4 _position; in vec2 _texCoord0; @@ -30,16 +27,18 @@ in vec3 _normal; in vec3 _color; void main(void) { - vec4 diffuse = texture(diffuseMap, _texCoord0); - vec4 emissive = texture(emissiveMap, _texCoord1); - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness)$> + <$fetchMaterialLightmap(_texCoord1, emissive)$> + packDeferredFragmentLightmap( normalize(_normal), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - getMaterialSpecular(mat), - getMaterialShininess(mat), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat) * roughness, + getMaterialMetallic(mat), + getMaterialFresnel(mat), (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); } diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index eacc91245c..8696b70373 100755 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -28,7 +28,7 @@ out vec3 _normal; out vec3 _color; void main(void) { - // pass along the diffuse color in linear space + // pass along the color in linear space _color = colorToLinearRGB(inColor.xyz); // and the texture coordinates diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slf b/libraries/render-utils/src/model_lightmap_normal_map.slf index 78c802be51..9ccc6e5352 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map.slf @@ -16,15 +16,9 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the normal map texture -uniform sampler2D normalMap; - -// the emissive map texture and parameters -uniform sampler2D emissiveMap; -uniform vec2 emissiveParams; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL)$> +<$declareMaterialLightmap()$> in vec4 _position; in vec2 _texCoord0; @@ -34,25 +28,20 @@ in vec3 _tangent; in vec3 _color; void main(void) { - // compute the view normal from the various bits - vec3 normalizedNormal = normalize(_normal); - vec3 normalizedTangent = normalize(_tangent); - vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); - vec3 localNormal = vec3(texture(normalMap, _texCoord0)) - vec3(0.5, 0.5, 0.5); - vec4 viewNormal = vec4(normalizedTangent * localNormal.x + - normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); - - // set the diffuse, normal, specular data - vec4 diffuse = texture(diffuseMap, _texCoord0); - vec4 emissive = texture(emissiveMap, _texCoord1); - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, normalTexel)$> + <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> + + vec3 viewNormal; + <$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$> packDeferredFragmentLightmap( normalize(viewNormal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - getMaterialSpecular(mat), - getMaterialShininess(mat), - (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat), + getMaterialMetallic(mat), + getMaterialFresnel(mat), + lightmapVal); } diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index 6046a67e10..eb8f80656d 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -29,7 +29,7 @@ out vec3 _tangent; out vec3 _color; void main(void) { - // pass along the diffuse color in linear space + // pass along the color in linear space _color = colorToLinearRGB(inColor.xyz); // and the texture coordinates diff --git a/libraries/render-utils/src/model_lightmap_normal_specular_map.slf b/libraries/render-utils/src/model_lightmap_normal_specular_map.slf index 1b7416baa5..71909a789f 100755 --- a/libraries/render-utils/src/model_lightmap_normal_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_specular_map.slf @@ -16,18 +16,9 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the emissive map texture and parameters -uniform sampler2D emissiveMap; -uniform vec2 emissiveParams; - -// the normal map texture -uniform sampler2D normalMap; - -// the specular map texture -uniform sampler2D specularMap; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC)$> +<$declareMaterialLightmap()$> in vec4 _position; in vec2 _texCoord0; @@ -37,26 +28,20 @@ in vec3 _tangent; in vec3 _color; void main(void) { - // compute the view normal from the various bits - vec3 normalizedNormal = normalize(_normal); - vec3 normalizedTangent = normalize(_tangent); - vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); - vec3 localNormal = vec3(texture(normalMap, _texCoord0)) - vec3(0.5, 0.5, 0.5); - vec4 viewNormal = vec4(normalizedTangent * localNormal.x + - normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); - - // set the diffuse, normal, specular data - vec4 diffuse = texture(diffuseMap, _texCoord0); - vec3 specular = texture(specularMap, _texCoord0).rgb; - vec4 emissive = texture(emissiveMap, _texCoord1); - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$> + <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> + + vec3 viewNormal; + <$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$> packDeferredFragmentLightmap( normalize(viewNormal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - specular, // no use of getMaterialSpecular(mat) - getMaterialShininess(mat), - (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat) * roughness, + getMaterialMetallic(mat) * metallicTex, + /*specular, // no use of */ getMaterialFresnel(mat), + lightmapVal); } diff --git a/libraries/render-utils/src/model_lightmap_specular_map.slf b/libraries/render-utils/src/model_lightmap_specular_map.slf index efdfcd6be9..5eefefdc29 100755 --- a/libraries/render-utils/src/model_lightmap_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_specular_map.slf @@ -16,15 +16,9 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the emissive map texture and parameters -uniform sampler2D emissiveMap; -uniform vec2 emissiveParams; - -// the specular texture -uniform sampler2D specularMap; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC)$> +<$declareMaterialLightmap()$> in vec4 _position; in vec2 _texCoord0; @@ -33,18 +27,17 @@ in vec3 _normal; in vec3 _color; void main(void) { - // set the diffuse, normal, specular data - vec4 diffuse = texture(diffuseMap, _texCoord0); - vec3 specular = texture(specularMap, _texCoord0).rgb; - vec4 emissive = texture(emissiveMap, _texCoord1); - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedo, roughness, _SCRIBE_NULL, metallicTex)$> + <$fetchMaterialLightmap(_texCoord1, lightmapVal)$> packDeferredFragmentLightmap( normalize(_normal), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - specular, // no use of getMaterialSpecular(mat) - getMaterialShininess(mat), - (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat) * roughness, + getMaterialMetallic(mat) * metallicTex, + /*metallicTex, // no use of */getMaterialFresnel(mat), + lightmapVal); } diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index 5479cec8eb..a584427005 100755 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -16,11 +16,8 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the normal map texture -uniform sampler2D normalMap; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> in vec4 _position; in vec2 _texCoord0; @@ -29,22 +26,29 @@ in vec3 _tangent; in vec3 _color; void main(void) { - // compute the view normal from the various bits - vec3 normalizedNormal = normalize(_normal.xyz); - vec3 normalizedTangent = normalize(_tangent.xyz); - vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); - vec3 localNormal = normalize(vec3(texture(normalMap, _texCoord0.st)) - vec3(0.5, 0.5, 0.5)); - vec4 viewNormal = vec4(normalizedTangent * localNormal.x + - normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); - - vec4 diffuse = texture(diffuseMap, _texCoord0.st); - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + vec3 viewNormal; + <$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$> packDeferredFragment( - normalize(viewNormal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - getMaterialSpecular(mat), - getMaterialShininess(mat)); + viewNormal, + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + albedo, + roughness, + getMaterialMetallic(mat), + emissive, + occlusionTex); } diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index 5ed4e37278..f7711cd326 100755 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -28,7 +28,7 @@ out vec3 _tangent; out vec3 _color; void main(void) { - // pass along the diffuse color + // pass along the color _color = colorToLinearRGB(inColor.xyz); // and the texture coordinates diff --git a/libraries/render-utils/src/model_normal_specular_map.slf b/libraries/render-utils/src/model_normal_specular_map.slf index 2ebcdaae44..cf461db7ef 100755 --- a/libraries/render-utils/src/model_normal_specular_map.slf +++ b/libraries/render-utils/src/model_normal_specular_map.slf @@ -16,14 +16,8 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the normal map texture -uniform sampler2D normalMap; - -// the specular map texture -uniform sampler2D specularMap; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> in vec4 _position; in vec2 _texCoord0; @@ -32,24 +26,33 @@ in vec3 _tangent; in vec3 _color; void main(void) { - // compute the view normal from the various bits - vec3 normalizedNormal = normalize(_normal); - vec3 normalizedTangent = normalize(_tangent); - vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); - vec3 localNormal = normalize(vec3(texture(normalMap, _texCoord0)) - vec3(0.5, 0.5, 0.5)); - vec4 viewNormal = vec4(normalizedTangent * localNormal.x + - normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); - - // set the diffuse, normal, specular data - vec4 diffuse = texture(diffuseMap, _texCoord0); - vec3 specular = texture(specularMap, _texCoord0).rgb; - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$> + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + vec3 viewNormal; + <$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$> + + float metallic = getMaterialMetallic(mat); + <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + packDeferredFragment( normalize(viewNormal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - specular, //getMaterialSpecular(mat), - getMaterialShininess(mat)); + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + albedo, + roughness, + metallic, + emissive, + occlusionTex); } diff --git a/libraries/render-utils/src/model_specular_map.slf b/libraries/render-utils/src/model_specular_map.slf index e7f639194d..32e5823430 100755 --- a/libraries/render-utils/src/model_specular_map.slf +++ b/libraries/render-utils/src/model_specular_map.slf @@ -16,11 +16,8 @@ <@include model/Material.slh@> -// the diffuse texture -uniform sampler2D diffuseMap; - -// the specular texture -uniform sampler2D specularMap; +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> in vec4 _position; in vec2 _texCoord0; @@ -29,16 +26,29 @@ in vec3 _color; void main(void) { - // set the diffuse, normal, specular data - vec4 diffuse = texture(diffuseMap, _texCoord0); - vec3 specular = texture(specularMap, _texCoord0).rgb; - Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$> + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + float metallic = getMaterialMetallic(mat); + <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; packDeferredFragment( normalize(_normal), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * _color, - specular, //getMaterialSpecular(mat), - getMaterialShininess(mat)); + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + albedo, + roughness, + metallic, + emissive, + occlusionTex); } diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 481cb942b3..086f9fe168 100755 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -20,9 +20,7 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> - - -uniform sampler2D diffuseMap; +uniform sampler2D albedoMap; in vec2 _texCoord0; in vec4 _position; @@ -33,15 +31,16 @@ in float _alpha; out vec4 _fragColor; void main(void) { - vec4 diffuse = texture(diffuseMap, _texCoord0); + vec4 albedo = texture(albedoMap, _texCoord0); Material mat = getMaterial(); vec3 fragPosition = _position.xyz; vec3 fragNormal = normalize(_normal); - vec3 fragDiffuse = getMaterialDiffuse(mat) * diffuse.rgb * _color; - vec3 fragSpecular = getMaterialSpecular(mat); - float fragGloss = getMaterialShininess(mat) / 128; - float fragOpacity = getMaterialOpacity(mat) * diffuse.a * _alpha; + vec3 fragAlbedo = getMaterialAlbedo(mat) * albedo.rgb * _color; + float fragMetallic = getMaterialMetallic(mat); + vec3 fragEmissive = getMaterialEmissive(mat); + float fragRoughness = getMaterialRoughness(mat); + float fragOpacity = getMaterialOpacity(mat) * albedo.a * _alpha; TransformCamera cam = getTransformCamera(); @@ -51,8 +50,9 @@ void main(void) { 1.0, fragPosition, fragNormal, - fragDiffuse, - fragSpecular, - fragGloss), + fragAlbedo, + fragMetallic, + fragEmissive, + fragRoughness), fragOpacity); } diff --git a/libraries/render-utils/src/model_translucent_emissive.slf b/libraries/render-utils/src/model_translucent_emissive.slf index a2c7186f6f..82faf7cf45 100644 --- a/libraries/render-utils/src/model_translucent_emissive.slf +++ b/libraries/render-utils/src/model_translucent_emissive.slf @@ -14,7 +14,7 @@ <@include model/Material.slh@> -uniform sampler2D diffuseMap; +uniform sampler2D albedoMap; in vec2 _texCoord0; in vec3 _color; @@ -23,11 +23,11 @@ in float _alpha; out vec4 _fragColor; void main(void) { - vec4 diffuse = texture(diffuseMap, _texCoord0); + vec4 albedo = texture(albedoMap, _texCoord0); Material mat = getMaterial(); - vec3 fragColor = getMaterialDiffuse(mat) * diffuse.rgb * _color; - float fragOpacity = getMaterialOpacity(mat) * diffuse.a * _alpha; + vec3 fragColor = getMaterialAlbedo(mat) * albedo.rgb * _color; + float fragOpacity = getMaterialOpacity(mat) * albedo.a * _alpha; _fragColor = vec4(fragColor, fragOpacity); } diff --git a/libraries/render-utils/src/overlay3D.slf b/libraries/render-utils/src/overlay3D.slf index 66a48e95f4..38199a7a82 100644 --- a/libraries/render-utils/src/overlay3D.slf +++ b/libraries/render-utils/src/overlay3D.slf @@ -17,7 +17,7 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss, float opacity) { +vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 specular, float roughness, float opacity) { // Need the light now Light light = getLight(); @@ -28,11 +28,11 @@ vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 d vec3 fragEyeDir; <$transformEyeToWorldDir(cam, fragEyeVectorView, fragEyeDir)$> - vec3 color = opacity * diffuse.rgb * getLightColor(light) * getLightAmbientIntensity(light); + vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(light); - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); + vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, metallic, specular, roughness); - color += vec3(diffuse * shading.w * opacity + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + color += vec3(albedo * shading.w * opacity + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); return vec4(color, opacity); } @@ -48,14 +48,15 @@ in float _alpha; out vec4 _fragColor; void main(void) { - vec4 diffuse = texture(originalTexture, _texCoord0); + vec4 albedo = texture(originalTexture, _texCoord0); vec3 fragPosition = _position.xyz; vec3 fragNormal = normalize(_normal); - vec3 fragDiffuse = diffuse.rgb * _color; + vec3 fragAlbedo = albedo.rgb * _color; + float fragMetallic = 0.0; vec3 fragSpecular = vec3(0.1); - float fragGloss = 10.0 / 128.0; - float fragOpacity = diffuse.a; + float fragRoughness = 0.9; + float fragOpacity = albedo.a; if (fragOpacity <= 0.1) { discard; @@ -64,9 +65,10 @@ void main(void) { vec4 color = evalGlobalColor(1.0, fragPosition, fragNormal, - fragDiffuse, + fragAlbedo, + fragMetallic, fragSpecular, - fragGloss, + fragRoughness, fragOpacity); // Apply standard tone mapping diff --git a/libraries/render-utils/src/overlay3D_emissive.slf b/libraries/render-utils/src/overlay3D_emissive.slf index ad689baf91..727eb0f317 100644 --- a/libraries/render-utils/src/overlay3D_emissive.slf +++ b/libraries/render-utils/src/overlay3D_emissive.slf @@ -20,12 +20,12 @@ in vec3 _color; out vec4 _fragColor; void main(void) { - vec4 diffuse = texture(originalTexture, _texCoord0); + vec4 albedo = texture(originalTexture, _texCoord0); - if (diffuse.a <= 0.1) { + if (albedo.a <= 0.1) { discard; } - vec4 color = vec4(diffuse.rgb * _color, diffuse.a); + vec4 color = vec4(albedo.rgb * _color, albedo.a); // Apply standard tone mapping _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf index dcc15ba25b..f8c18abf20 100644 --- a/libraries/render-utils/src/overlay3D_translucent.slf +++ b/libraries/render-utils/src/overlay3D_translucent.slf @@ -18,7 +18,7 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss, float opacity) { +vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 specular, float roughness, float opacity) { // Need the light now Light light = getLight(); @@ -29,11 +29,11 @@ vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 d vec3 fragEyeDir; <$transformEyeToWorldDir(cam, fragEyeVectorView, fragEyeDir)$> - vec3 color = opacity * diffuse.rgb * getLightColor(light) * getLightAmbientIntensity(light); + vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(light); - vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); + vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, metallic, specular, roughness); - color += vec3(diffuse * shading.w * opacity + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); + color += vec3(albedo * shading.w * opacity + shading.rgb) * shadowAttenuation * getLightColor(light) * getLightIntensity(light); return vec4(color, opacity); } @@ -49,21 +49,23 @@ in float _alpha; out vec4 _fragColor; void main(void) { - vec4 diffuse = texture(originalTexture, _texCoord0); + vec4 albedo = texture(originalTexture, _texCoord0); vec3 fragPosition = _position.xyz; vec3 fragNormal = normalize(_normal); - vec3 fragDiffuse = diffuse.rgb * _color; + vec3 fragAlbedo = albedo.rgb * _color; + float fragMetallic = 0.0; vec3 fragSpecular = vec3(0.1); - float fragGloss = 10.0 / 128.0; - float fragOpacity = diffuse.a * _alpha; + float fragRoughness = 0.9; + float fragOpacity = albedo.a * _alpha; vec4 color = evalGlobalColor(1.0, fragPosition, fragNormal, - fragDiffuse, + fragAlbedo, + fragMetallic, fragSpecular, - fragGloss, + fragRoughness, fragOpacity); // Apply standard tone mapping diff --git a/libraries/render-utils/src/overlay3D_translucent_emissive.slf b/libraries/render-utils/src/overlay3D_translucent_emissive.slf index bcfec7d588..61935f3c67 100644 --- a/libraries/render-utils/src/overlay3D_translucent_emissive.slf +++ b/libraries/render-utils/src/overlay3D_translucent_emissive.slf @@ -21,7 +21,7 @@ in float _alpha; out vec4 _fragColor; void main(void) { - vec4 diffuse = texture(originalTexture, _texCoord0); + vec4 albedo = texture(originalTexture, _texCoord0); - _fragColor = vec4(diffuse.rgb * _color, diffuse.a * _alpha); + _fragColor = vec4(albedo.rgb * _color, albedo.a * _alpha); } diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index a64d4a81a8..0cadf3a760 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -60,7 +60,7 @@ void main(void) { vec3 fragNormal = vec3(invViewMat * vec4(frag.normal, 0.0)); vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.specular, frag.gloss); + vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); // Eval attenuation float radialAttenuation = evalLightAttenuation(light, fragLightDistance); diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 69fec0042b..1e11cd984d 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -10,6 +10,8 @@ // 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@> + uniform sampler2D Font; uniform bool Outline; uniform vec4 Color; @@ -18,17 +20,13 @@ uniform vec4 Color; in vec3 _normal; in vec2 _texCoord0; -layout(location = 0) out vec4 _fragColor0; -layout(location = 1) out vec4 _fragColor1; -layout(location = 2) out vec4 _fragColor2; - -const float DEFAULT_SHININESS = 10; - const float gamma = 2.2; const float smoothing = 256.0; const float interiorCutoff = 0.8; const float outlineExpansion = 0.2; + + void main() { // retrieve signed distance float sdf = texture(Font, _texCoord0).g; @@ -48,12 +46,17 @@ void main() { // gamma correction for linear attenuation a = pow(a, 1.0 / gamma); + // discard if unvisible if (a < 0.01) { discard; } - // final color - _fragColor0 = vec4(Color.rgb, Color.a * a); - _fragColor1 = vec4(normalize(_normal.xyz), 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); - _fragColor2 = vec4(Color.rgb, DEFAULT_SHININESS / 128.0); + packDeferredFragmentLightmap( + normalize(_normal), + 1.0, + vec3(1.0), + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_SPECULAR, + Color.rgb); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index b24e4f92ff..5ad9dc174f 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -51,9 +51,9 @@ void main(void) { if (emissiveAmount > 0.0) { packDeferredFragmentLightmap( - normal, 1.0, diffuse, specular, shininess, specular); + normal, 1.0, diffuse, max(0, 1.0 - shininess / 128.0), DEFAULT_METALLIC, specular, specular); } else { packDeferredFragment( - normal, 1.0, diffuse, specular, shininess); + normal, 1.0, diffuse, max(0, 1.0 - shininess / 128.0), length(specular), DEFAULT_EMISSIVE, DEFAULT_OCCLUSION); } } diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 727c029bbe..0832b22214 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple.frag +// simple_textured.slf // fragment shader // // Created by Clément Brisset on 5/29/15. @@ -15,7 +15,7 @@ <@include DeferredBufferWrite.slh@> <@include model/Material.slh@> -// the diffuse texture +// the albedo texture uniform sampler2D originalTexture; // the interpolated normal @@ -31,5 +31,8 @@ void main(void) { normalize(_normal.xyz), texel.a, _color.rgb * texel.rgb, - DEFAULT_SPECULAR, DEFAULT_SHININESS); + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_OCCLUSION); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_textured_emisive.slf b/libraries/render-utils/src/simple_textured_emisive.slf index 1dd3d667a6..92bdee8d02 100644 --- a/libraries/render-utils/src/simple_textured_emisive.slf +++ b/libraries/render-utils/src/simple_textured_emisive.slf @@ -14,7 +14,7 @@ <@include DeferredBufferWrite.slh@> -// the diffuse texture +// the albedo texture uniform sampler2D originalTexture; // the interpolated normal @@ -29,6 +29,8 @@ void main(void) { normalize(_normal), texel.a, _color.rgb, - DEFAULT_SPECULAR, DEFAULT_SHININESS, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_SPECULAR, texel.rgb); } \ No newline at end of file diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index 8ab475e1fd..199b97ba53 100755 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -32,7 +32,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); - // pass along the diffuse color + // pass along the color _color = colorToLinearRGB(inColor.rgb); // and the texture coordinates diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index cec93a024e..003b35b79b 100755 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -34,7 +34,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); - // pass along the diffuse color + // pass along the color _color = colorToLinearRGB(inColor.rgb); // and the texture coordinates diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index b72598f810..69f8e836aa 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -66,7 +66,7 @@ void main(void) { vec3 fragNormal = vec3(invViewMat * vec4(frag.normal, 0.0)); vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.specular, frag.gloss); + vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); // Eval attenuation float radialAttenuation = evalLightAttenuation(light, fragLightDistance); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 2e86d09fe0..762a7fb723 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include "sdf_text3D_vert.h" @@ -354,7 +356,10 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c batch.setPipeline(layered ? _layeredPipeline : _pipeline); batch.setResourceTexture(_fontLoc, _texture); batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT)); - batch._glUniform4fv(_colorLoc, 1, (const float*)color); + + // need the gamma corrected color here + glm::vec4 lrgba = glm::vec4(ColorUtils::toLinearVec3(glm::vec3(*color)), color->a); + batch._glUniform4fv(_colorLoc, 1, (const float*)&lrgba); batch.setInputFormat(_format); batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index b77c9c3451..6974a7e385 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -53,10 +53,12 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::SKINNING_GPU)); slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::MATERIAL_GPU)); - slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), Slot::DIFFUSE_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::ALBEDO_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::ROUGHNESS_MAP)); slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::NORMAL_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), Slot::SPECULAR_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::LIGHTMAP_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::METALLIC_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::EMISSIVE_LIGHTMAP_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::OCCLUSION_MAP)); slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::LIGHT_BUFFER)); slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING_MAP)); @@ -66,10 +68,12 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->texcoordMatrices = program->getUniforms().findLocation("texcoordMatrices"); locations->emissiveParams = program->getUniforms().findLocation("emissiveParams"); locations->normalFittingMapUnit = program->getTextures().findLocation("normalFittingMap"); - locations->diffuseTextureUnit = program->getTextures().findLocation("diffuseMap"); + locations->albedoTextureUnit = program->getTextures().findLocation("albedoMap"); + locations->roughnessTextureUnit = program->getTextures().findLocation("roughnessMap"); locations->normalTextureUnit = program->getTextures().findLocation("normalMap"); - locations->specularTextureUnit = program->getTextures().findLocation("specularMap"); + locations->metallicTextureUnit = program->getTextures().findLocation("metallicMap"); locations->emissiveTextureUnit = program->getTextures().findLocation("emissiveMap"); + locations->occlusionTextureUnit = program->getTextures().findLocation("occlusionMap"); locations->skinClusterBufferUnit = program->getBuffers().findLocation("skinClusterBuffer"); locations->materialBufferUnit = program->getBuffers().findLocation("materialBuffer"); locations->lightBufferUnit = program->getBuffers().findLocation("lightBuffer"); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 4fd1dc22c5..0f795aadde 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -195,10 +195,13 @@ public: public: static const int SKINNING_GPU = 2; static const int MATERIAL_GPU = 3; - static const int DIFFUSE_MAP = 0; + static const int ALBEDO_MAP = 0; static const int NORMAL_MAP = 1; - static const int SPECULAR_MAP = 2; - static const int LIGHTMAP_MAP = 3; + static const int METALLIC_MAP = 2; + static const int EMISSIVE_LIGHTMAP_MAP = 3; + static const int ROUGHNESS_MAP = 4; + static const int OCCLUSION_MAP = 5; + static const int LIGHT_BUFFER = 4; static const int NORMAL_FITTING_MAP = 10; }; @@ -206,10 +209,12 @@ public: class Locations { public: int texcoordMatrices; - int diffuseTextureUnit; + int albedoTextureUnit; int normalTextureUnit; - int specularTextureUnit; + int roughnessTextureUnit; + int metallicTextureUnit; int emissiveTextureUnit; + int occlusionTextureUnit; int emissiveParams; int normalFittingMapUnit; int skinClusterBufferUnit; diff --git a/libraries/script-engine/src/SceneScriptingInterface.cpp b/libraries/script-engine/src/SceneScriptingInterface.cpp index 079cfff2c7..3883b948df 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.cpp +++ b/libraries/script-engine/src/SceneScriptingInterface.cpp @@ -81,6 +81,11 @@ void SceneScripting::KeyLight::setAmbientSphere(const gpu::SHPointer& sphere) { _skyStage->setSunAmbientSphere(sphere); } +void SceneScripting::KeyLight::setAmbientMap(const gpu::TexturePointer& map) { + _skyStage->setSunAmbientMap(map); +} + + glm::vec3 SceneScripting::KeyLight::getDirection() const { return _skyStage->getSunDirection(); } diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 12681b1887..e8ea2e0217 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -84,6 +84,7 @@ namespace SceneScripting { // AmbientTexture is unscriptable - it must be set through the zone entity void setAmbientSphere(const gpu::SHPointer& sphere); void resetAmbientSphere() { setAmbientSphere(nullptr); } + void setAmbientMap(const gpu::TexturePointer& map); protected: model::SunSkyStagePointer _skyStage; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 738e313f8f..9ee2c374c5 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -485,10 +485,10 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin return; } RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; - QScriptValueList& handlersForEvent = handlersOnEntity[eventName]; + CallbackList& handlersForEvent = handlersOnEntity[eventName]; // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate. for (int i = 0; i < handlersForEvent.count(); ++i) { - if (handlersForEvent[i].equals(handler)) { + if (handlersForEvent[i].function.equals(handler)) { handlersForEvent.removeAt(i); return; // Design choice: since comparison is relatively expensive, just remove the first matching handler. } @@ -516,6 +516,13 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& // Connect up ALL the handlers to the global entities object's signals. // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) auto entities = DependencyManager::get(); + // Bug? These handlers are deleted when entityID is deleted, which is nice. + // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads? + // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow, + // and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded? + // Also, what about when the entity script is REloaded? + // For now, we are leaving them around. Changing that would require some non-trivial digging around to find the + // handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { _registeredHandlers.remove(entityID); }); @@ -563,8 +570,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& if (!_registeredHandlers.contains(entityID)) { _registeredHandlers[entityID] = RegisteredEventHandlers(); } - QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName]; - handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler(). + CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; + CallbackData handlerData = {handler, currentEntityIdentifier}; + handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler(). } @@ -705,13 +713,30 @@ void ScriptEngine::run() { // NOTE: This is private because it must be called on the same thread that created the timers, which is why // we want to only call it in our own run "shutdown" processing. void ScriptEngine::stopAllTimers() { - QMutableHashIterator i(_timerFunctionMap); + QMutableHashIterator i(_timerFunctionMap); while (i.hasNext()) { i.next(); QTimer* timer = i.key(); stopTimer(timer); } } +void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { + // We could maintain a separate map of entityID => QTimer, but someone will have to prove to me that it's worth the complexity. -HRS + QVector toDelete; + QMutableHashIterator i(_timerFunctionMap); + while (i.hasNext()) { + i.next(); + if (i.value().definingEntityIdentifier != entityID) { + continue; + } + QTimer* timer = i.key(); + toDelete << timer; // don't delete while we're iterating. save it. + } + for (auto timer:toDelete) { // now reap 'em + stopTimer(timer); + } + +} void ScriptEngine::stop() { if (!_isFinished) { @@ -743,13 +768,14 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames); QScriptValueList callingArguments; callingArguments << javascriptParameters; + assert(currentEntityIdentifier.isInvalidID()); // No animation state handlers from entity scripts. QScriptValue result = callback.call(QScriptValue(), callingArguments); resultHandler(result); } void ScriptEngine::timerFired() { QTimer* callingTimer = reinterpret_cast(sender()); - QScriptValue timerFunction = _timerFunctionMap.value(callingTimer); + CallbackData timerData = _timerFunctionMap.value(callingTimer); if (!callingTimer->isActive()) { // this timer is done, we can kill it @@ -758,11 +784,12 @@ void ScriptEngine::timerFired() { } // call the associated JS function, if it exists - if (timerFunction.isValid()) { - timerFunction.call(); + if (timerData.function.isValid()) { + callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList()); } } + QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) { // create the timer, add it to the map, and start it QTimer* newTimer = new QTimer(this); @@ -773,7 +800,8 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int // make sure the timer stops when the script does connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); - _timerFunctionMap.insert(newTimer, function); + CallbackData timerData = {function, currentEntityIdentifier}; + _timerFunctionMap.insert(newTimer, timerData); newTimer->start(intervalMS); return newTimer; @@ -859,6 +887,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac } BatchLoader* loader = new BatchLoader(urls); + EntityItemID capturedEntityIdentifier = currentEntityIdentifier; auto evaluateScripts = [=](const QMap& data) { auto parentURL = _parentURL; @@ -870,13 +899,16 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac // Set the parent url so that path resolution will be relative // to this script's url during its initial evaluation _parentURL = url.toString(); - QScriptValue result = evaluate(contents, url.toString()); + auto operation = [&]() { + evaluate(contents, url.toString()); + }; + doWithEnvironment(capturedEntityIdentifier, operation); } } _parentURL = parentURL; if (callback.isFunction()) { - QScriptValue(callback).call(); + callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList()); } loader->deleteLater(); @@ -917,6 +949,11 @@ void ScriptEngine::load(const QString& loadFile) { << "loadFile:" << loadFile << "parent script:" << getFilename(); return; // bail early } + if (!currentEntityIdentifier.isInvalidID()) { + qCWarning(scriptengine) << "Script.load() from entity script is ignored... " + << "loadFile:" << loadFile << "parent script:" << getFilename(); + return; // bail early + } QUrl url = resolvePath(loadFile); if (_isReloading) { @@ -933,7 +970,7 @@ void ScriptEngine::load(const QString& loadFile) { } // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args -void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) { +void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) { if (QThread::currentThread() != thread()) { qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; assert(false); @@ -946,10 +983,13 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin if (!handlersOnEntity.contains(eventName)) { return; } - QScriptValueList handlersForEvent = handlersOnEntity[eventName]; + CallbackList handlersForEvent = handlersOnEntity[eventName]; if (!handlersForEvent.isEmpty()) { for (int i = 0; i < handlersForEvent.count(); ++i) { - handlersForEvent[i].call(QScriptValue(), eventHanderArgs); + // handlersForEvent[i] can tonain many handlers that may have each been added by different interface or entity scripts, + // and the entity scripts may be for entities other than the one this is a handler for. + // Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added. + callWithEnvironment(handlersForEvent[i].definingEntityIdentifier, handlersForEvent[i].function, QScriptValue(), eventHandlerArgs); } } } @@ -1055,9 +1095,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co QString file = QUrl(scriptOrURL).toLocalFile(); lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); } + QScriptValue entityScriptConstructor, entityScriptObject; + auto initialization = [&]{ + entityScriptConstructor = evaluate(contents, fileName); + entityScriptObject = entityScriptConstructor.construct(); + }; + doWithEnvironment(entityID, initialization); - QScriptValue entityScriptConstructor = evaluate(contents, fileName); - QScriptValue entityScriptObject = entityScriptConstructor.construct(); EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; _entityScripts[entityID] = newDetails; if (isURL) { @@ -1087,6 +1131,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { if (_entityScripts.contains(entityID)) { callEntityScriptMethod(entityID, "unload"); _entityScripts.remove(entityID); + stopAllTimersForEntityScript(entityID); } } @@ -1142,6 +1187,32 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { recurseGuard = false; } +// Execute operation in the appropriate context for (the possibly empty) entityID. +// Even if entityID is supplied as currentEntityIdentifier, this still documents the source +// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different +// global values for different entity scripts). +void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function operation) { + EntityItemID oldIdentifier = currentEntityIdentifier; + currentEntityIdentifier = entityID; + +#if DEBUG_CURRENT_ENTITY + QScriptValue oldData = this->globalObject().property("debugEntityID"); + this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global. + operation(); + this->globalObject().setProperty("debugEntityID", oldData); +#else + operation(); +#endif + + currentEntityIdentifier = oldIdentifier; +} +void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { + auto operation = [&]() { + function.call(thisObject, args); + }; + doWithEnvironment(entityID, operation); +} + void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING @@ -1168,7 +1239,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS QScriptValueList args; args << entityID.toScriptValue(this); args << qScriptValueFromSequence(this, params); - entityScript.property(methodName).call(entityScript, args); + callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); } } @@ -1200,7 +1271,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS QScriptValueList args; args << entityID.toScriptValue(this); args << event.toScriptValue(this); - entityScript.property(methodName).call(entityScript, args); + callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); } } } @@ -1234,7 +1305,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS args << entityID.toScriptValue(this); args << otherID.toScriptValue(this); args << collisionToScriptValue(this, collision); - entityScript.property(methodName).call(entityScript, args); + callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); } } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index f89998d9f0..91d4c3f5a5 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -42,7 +42,14 @@ const QString NO_SCRIPT(""); const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1000) + 0.5f); -typedef QHash RegisteredEventHandlers; +class CallbackData { +public: + QScriptValue function; + EntityItemID definingEntityIdentifier; +}; + +typedef QList CallbackList; +typedef QHash RegisteredEventHandlers; class EntityScriptDetails { public: @@ -169,7 +176,7 @@ protected: std::atomic _isRunning { false }; int _evaluatesPending { 0 }; bool _isInitialized { false }; - QHash _timerFunctionMap; + QHash _timerFunctionMap; QSet _includedURLs; bool _wantSignals { true }; QHash _entityScripts; @@ -181,6 +188,7 @@ protected: bool evaluatePending() const { return _evaluatesPending > 0; } void timerFired(); void stopAllTimers(); + void stopAllTimersForEntityScript(const EntityItemID& entityID); void refreshFileScript(const EntityItemID& entityID); void setParentURL(const QString& parentURL) { _parentURL = parentURL; } @@ -203,6 +211,10 @@ protected: void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); + EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. + void doWithEnvironment(const EntityItemID& entityID, std::function operation); + void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args); + friend class ScriptEngines; static std::atomic _stoppingAllScripts; }; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index a1aae35063..94d9b0ae26 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,19 @@ void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3) { vec3.z = object.property("z").toVariant().toFloat(); } +QVariant vec3toVariant(const glm::vec3 &vec3) { + if (vec3.x != vec3.x || vec3.y != vec3.y || vec3.z != vec3.z) { + // if vec3 contains a NaN don't try to convert it + return QVariant(); + } + QVariantMap result; + result["x"] = vec3.x; + result["y"] = vec3.y; + result["z"] = vec3.z; + return result; +} + + QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector& vector) { QScriptValue array = engine->newArray(); for (int i = 0; i < vector.size(); i++) { @@ -94,6 +108,7 @@ QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVectornewObject(); if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z || quat.w != quat.w) { @@ -195,6 +209,19 @@ glm::quat quatFromVariant(const QVariant &object) { return quatFromVariant(object, valid); } +QVariant quatToVariant(const glm::quat &quat) { + if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z) { + // if vec3 contains a NaN don't try to convert it + return QVariant(); + } + QVariantMap result; + result["x"] = quat.x; + result["y"] = quat.y; + result["z"] = quat.z; + result["w"] = quat.w; + return result; +} + QScriptValue qVectorQuatToScriptValue(QScriptEngine* engine, const QVector& vector) { QScriptValue array = engine->newArray(); for (int i = 0; i < vector.size(); i++) { @@ -333,6 +360,51 @@ void vec2FromScriptValue(const QScriptValue &object, glm::vec2 &vec2) { vec2.y = object.property("y").toVariant().toFloat(); } +QVariant vec2toVariant(const glm::vec2 &vec2) { + if (vec2.x != vec2.x || vec2.y != vec2.y) { + // if vec2 contains a NaN don't try to convert it + return QVariant(); + } + QVariantMap result; + result["x"] = vec2.x; + result["y"] = vec2.y; + return result; +} + +glm::vec2 vec2FromVariant(const QVariant &object, bool& isValid) { + isValid = false; + glm::vec2 result; + if (object.canConvert()) { + result = glm::vec2(object.toFloat()); + } else if (object.canConvert()) { + auto qvec2 = qvariant_cast(object); + result.x = qvec2.x(); + result.y = qvec2.y(); + } else { + auto map = object.toMap(); + auto x = map["x"]; + if (!x.isValid()) { + x = map["width"]; + } + auto y = map["y"]; + if (!y.isValid()) { + y = map["height"]; + } + if (x.isValid() && y.isValid()) { + result.x = x.toFloat(&isValid); + if (isValid) { + result.y = y.toFloat(&isValid); + } + } + } + return result; +} + +glm::vec2 vec2FromVariant(const QVariant &object) { + bool valid; + return vec2FromVariant(object, valid); +} + QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect) { QScriptValue obj = engine->newObject(); obj.setProperty("x", rect.x()); @@ -357,6 +429,38 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) { return obj; } +QVariant qRectToVariant(const QRect& rect) { + QVariantMap obj; + obj["x"] = rect.x(); + obj["y"] = rect.y(); + obj["width"] = rect.width(); + obj["height"] = rect.height(); + return obj; +} + +QRect qRectFromVariant(const QVariant& objectVar, bool& valid) { + QVariantMap object = objectVar.toMap(); + QRect rect; + valid = false; + rect.setX(object["x"].toInt(&valid)); + if (valid) { + rect.setY(object["y"].toInt(&valid)); + } + if (valid) { + rect.setWidth(object["width"].toInt(&valid)); + } + if (valid) { + rect.setHeight(object["height"].toInt(&valid)); + } + return rect; +} + +QRect qRectFromVariant(const QVariant& object) { + bool valid; + return qRectFromVariant(object, valid); +} + + void xColorFromScriptValue(const QScriptValue &object, xColor& color) { if (!object.isValid()) { return; @@ -377,6 +481,59 @@ void xColorFromScriptValue(const QScriptValue &object, xColor& color) { } } + +QVariant xColorToVariant(const xColor& color) { + QVariantMap obj; + obj["red"] = color.red; + obj["green"] = color.green; + obj["blue"] = color.blue; + return obj; +} + +xColor xColorFromVariant(const QVariant &object, bool& isValid) { + isValid = false; + xColor color { 0, 0, 0 }; + if (!object.isValid()) { + return color; + } + if (object.canConvert()) { + isValid = true; + color.red = color.green = color.blue = (uint8_t)object.toInt(); + } else if (object.canConvert()) { + QColor qcolor(object.toString()); + if (qcolor.isValid()) { + isValid = true; + color.red = (uint8_t)qcolor.red(); + color.blue = (uint8_t)qcolor.blue(); + color.green = (uint8_t)qcolor.green(); + } + } else if (object.canConvert()) { + QColor qcolor = qvariant_cast(object); + if (qcolor.isValid()) { + isValid = true; + color.red = (uint8_t)qcolor.red(); + color.blue = (uint8_t)qcolor.blue(); + color.green = (uint8_t)qcolor.green(); + } + } else { + QVariantMap map = object.toMap(); + color.red = map["red"].toInt(&isValid); + if (isValid) { + color.green = map["green"].toInt(&isValid); + } + if (isValid) { + color.blue = map["blue"].toInt(&isValid); + } + } + return color; +} + +xColor xColorFromVariant(const QVariant &object) { + bool valid; + return xColorFromVariant(object, valid); +} + + QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { QScriptValue object = engine->newObject(); object.setProperty("red", color.red()); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 2bec5b025e..0a3e94a5b6 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -35,48 +35,71 @@ Q_DECLARE_METATYPE(AACube) void registerMetaTypes(QScriptEngine* engine); +// Vec4 QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4); void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4); +// Vec3 QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3); void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3); +QVariant vec3toVariant(const glm::vec3 &vec3); glm::vec3 vec3FromVariant(const QVariant &object, bool& valid); glm::vec3 vec3FromVariant(const QVariant &object); +// Vec2 QScriptValue vec2toScriptValue(QScriptEngine* engine, const glm::vec2 &vec2); void vec2FromScriptValue(const QScriptValue &object, glm::vec2 &vec2); +QVariant vec2toVariant(const glm::vec2 &vec2); +glm::vec2 vec2FromVariant(const QVariant &object, bool& valid); +glm::vec2 vec2FromVariant(const QVariant &object); + +// Quaternions QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat); void quatFromScriptValue(const QScriptValue &object, glm::quat& quat); +QVariant quatToVariant(const glm::quat& quat); glm::quat quatFromVariant(const QVariant &object, bool& isValid); glm::quat quatFromVariant(const QVariant &object); +// Rect QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect); void qRectFromScriptValue(const QScriptValue& object, QRect& rect); +QVariant qRectToVariant(const QRect& rect); +QRect qRectFromVariant(const QVariant& object, bool& isValid); + +// xColor QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color); void xColorFromScriptValue(const QScriptValue &object, xColor& color); +QVariant xColorToVariant(const xColor& color); +xColor xColorFromVariant(const QVariant &object, bool& isValid); + +// QColor QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color); void qColorFromScriptValue(const QScriptValue& object, QColor& color); QScriptValue qURLToScriptValue(QScriptEngine* engine, const QUrl& url); void qURLFromScriptValue(const QScriptValue& object, QUrl& url); +// vector QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector& vector); void qVectorVec3FromScriptValue(const QScriptValue& array, QVector& vector); QVector qVectorVec3FromScriptValue(const QScriptValue& array); +// vector QScriptValue qVectorQuatToScriptValue(QScriptEngine* engine, const QVector& vector); void qVectorQuatFromScriptValue(const QScriptValue& array, QVector& vector); QVector qVectorQuatFromScriptValue(const QScriptValue& array); +// vector QScriptValue qVectorBoolToScriptValue(QScriptEngine* engine, const QVector& vector); void qVectorBoolFromScriptValue(const QScriptValue& array, QVector& vector); QVector qVectorBoolFromScriptValue(const QScriptValue& array); +// vector QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector& vector); void qVectorFloatFromScriptValue(const QScriptValue& array, QVector& vector); QVector qVectorFloatFromScriptValue(const QScriptValue& array); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 557b25d9b2..c8dceaa905 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -15,6 +15,7 @@ #include "SpatiallyNestable.h" const float defaultAACubeSize = 1.0f; +const int maxParentingChain = 30; SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : _nestableType(nestableType), @@ -56,14 +57,14 @@ void SpatiallyNestable::setParentID(const QUuid& parentID) { }); } -Transform SpatiallyNestable::getParentTransform(bool& success) const { +Transform SpatiallyNestable::getParentTransform(bool& success, int depth) const { Transform result; SpatiallyNestablePointer parent = getParentPointer(success); if (!success) { return result; } if (parent) { - Transform parentTransform = parent->getTransform(_parentJointIndex, success); + Transform parentTransform = parent->getTransform(_parentJointIndex, success, depth + 1); result = parentTransform.setScale(1.0f); // TODO: scaling } return result; @@ -393,11 +394,11 @@ void SpatiallyNestable::setOrientation(const glm::quat& orientation) { glm::vec3 SpatiallyNestable::getVelocity(bool& success) const { glm::vec3 result; - glm::vec3 parentVelocity = getParentVelocity(success); + Transform parentTransform = getParentTransform(success); if (!success) { return result; } - Transform parentTransform = getParentTransform(success); + glm::vec3 parentVelocity = getParentVelocity(success); if (!success) { return result; } @@ -448,11 +449,11 @@ glm::vec3 SpatiallyNestable::getParentVelocity(bool& success) const { glm::vec3 SpatiallyNestable::getAngularVelocity(bool& success) const { glm::vec3 result; - glm::vec3 parentAngularVelocity = getParentAngularVelocity(success); + Transform parentTransform = getParentTransform(success); if (!success) { return result; } - Transform parentTransform = getParentTransform(success); + glm::vec3 parentAngularVelocity = getParentAngularVelocity(success); if (!success) { return result; } @@ -499,22 +500,36 @@ glm::vec3 SpatiallyNestable::getParentAngularVelocity(bool& success) const { return result; } -const Transform SpatiallyNestable::getTransform(bool& success) const { - // return a world-space transform for this object's location - Transform parentTransform = getParentTransform(success); +const Transform SpatiallyNestable::getTransform(bool& success, int depth) const { Transform result; + // return a world-space transform for this object's location + Transform parentTransform = getParentTransform(success, depth); _transformLock.withReadLock([&] { Transform::mult(result, parentTransform, _transform); }); return result; } -const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success) const { +const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success, int depth) const { // this returns the world-space transform for this object. It finds its parent's transform (which may // cause this object's parent to query its parent, etc) and multiplies this object's local transform onto it. Transform jointInWorldFrame; - Transform worldTransform = getTransform(success); + if (depth > maxParentingChain) { + success = false; + // someone created a loop. break it... + qDebug() << "Parenting loop detected."; + SpatiallyNestablePointer _this = getThisPointer(); + _this->setParentID(QUuid()); + bool setPositionSuccess; + AACube aaCube = getQueryAACube(setPositionSuccess); + if (setPositionSuccess) { + _this->setPosition(aaCube.calcCenter()); + } + return jointInWorldFrame; + } + + Transform worldTransform = getTransform(success, depth); worldTransform.setScale(1.0f); // TODO -- scale; if (!success) { return jointInWorldFrame; @@ -682,7 +697,7 @@ QList SpatiallyNestable::getChildren() const { _childrenLock.withReadLock([&] { foreach(SpatiallyNestableWeakPointer childWP, _children.values()) { SpatiallyNestablePointer child = childWP.lock(); - if (child) { + if (child && child->_parentKnowsMe && child->getParentID() == getID()) { children << child; } } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 2a8976b38f..ee6cccc7a8 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -52,10 +52,10 @@ public: static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool& success); // world frame - virtual const Transform getTransform(bool& success) const; + virtual const Transform getTransform(bool& success, int depth = 0) const; virtual void setTransform(const Transform& transform, bool& success); - virtual Transform getParentTransform(bool& success) const; + virtual Transform getParentTransform(bool& success, int depth = 0) const; virtual glm::vec3 getPosition(bool& success) const; virtual glm::vec3 getPosition() const; @@ -92,7 +92,7 @@ public: virtual void setScale(const glm::vec3& scale); // get world-frame values for a specific joint - virtual const Transform getTransform(int jointIndex, bool& success) const; + virtual const Transform getTransform(int jointIndex, bool& success, int depth = 0) const; virtual glm::vec3 getPosition(int jointIndex, bool& success) const; virtual glm::vec3 getScale(int jointIndex) const; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 071d5fd631..0de8f6891d 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -60,10 +60,10 @@ void ViveControllerManager::activate() { [this] (bool clicked) { this->setRenderControllers(clicked); }, true, true); - if (!_hmd) { - _hmd = acquireOpenVrSystem(); + if (!_system) { + _system = acquireOpenVrSystem(); } - Q_ASSERT(_hmd); + Q_ASSERT(_system); // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code @@ -71,7 +71,7 @@ void ViveControllerManager::activate() { auto renderModels = vr::VRRenderModels(); vr::RenderModel_t model; - if (!_hmd->LoadRenderModel(CONTROLLER_MODEL_STRING, &model)) { + if (!_system->LoadRenderModel(CONTROLLER_MODEL_STRING, &model)) { qDebug() << QString("Unable to load render model %1\n").arg(CONTROLLER_MODEL_STRING); } else { model::Mesh* mesh = new model::Mesh(); @@ -118,7 +118,7 @@ void ViveControllerManager::activate() { } */ - // unregister with UserInputMapper + // register with UserInputMapper auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; @@ -130,9 +130,9 @@ void ViveControllerManager::deactivate() { _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); - if (_hmd) { + if (_system) { releaseOpenVrSystem(); - _hmd = nullptr; + _system = nullptr; } _inputDevice->_poseStateMap.clear(); @@ -226,56 +226,56 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { _poseStateMap.clear(); - _buttonPressedMap.clear(); PerformanceTimer perfTimer("ViveControllerManager::update"); + auto leftHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand); + auto rightHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand); + + if (!jointsCaptured) { + handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true); + handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false); + } + int numTrackedControllers = 0; - - for (vr::TrackedDeviceIndex_t device = vr::k_unTrackedDeviceIndex_Hmd + 1; - device < vr::k_unMaxTrackedDeviceCount && numTrackedControllers < 2; ++device) { - - if (!_hmd->IsTrackedDeviceConnected(device)) { - continue; - } - - if (_hmd->GetTrackedDeviceClass(device) != vr::TrackedDeviceClass_Controller) { - continue; - } - - if (!_trackedDevicePose[device].bPoseIsValid) { - continue; - } - + if (leftHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) { numTrackedControllers++; - bool left = numTrackedControllers == 2; + } + if (rightHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) { + numTrackedControllers++; + } + _trackedControllers = numTrackedControllers; +} - if (!jointsCaptured) { - const mat4& mat = _trackedDevicePoseMat4[device]; - const vec3 linearVelocity = _trackedDeviceLinearVelocities[device]; - const vec3 angularVelocity = _trackedDeviceAngularVelocities[device]; - handlePoseEvent(inputCalibrationData, mat, linearVelocity, angularVelocity, numTrackedControllers - 1); - } +void ViveControllerManager::InputDevice::handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand) { + + if (_system->IsTrackedDeviceConnected(deviceIndex) && + _system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_Controller && + _trackedDevicePose[deviceIndex].bPoseIsValid) { + + // process pose + const mat4& mat = _trackedDevicePoseMat4[deviceIndex]; + const vec3 linearVelocity = _trackedDeviceLinearVelocities[deviceIndex]; + const vec3 angularVelocity = _trackedDeviceAngularVelocities[deviceIndex]; + handlePoseEvent(deltaTime, inputCalibrationData, mat, linearVelocity, angularVelocity, isLeftHand); - // handle inputs vr::VRControllerState_t controllerState = vr::VRControllerState_t(); - if (_hmd->GetControllerState(device, &controllerState)) { - //qDebug() << (numTrackedControllers == 1 ? "Left: " : "Right: "); - //qDebug() << "Trackpad: " << controllerState.rAxis[0].x << " " << controllerState.rAxis[0].y; - //qDebug() << "Trigger: " << controllerState.rAxis[1].x << " " << controllerState.rAxis[1].y; + if (_system->GetControllerState(deviceIndex, &controllerState)) { + + // process each button for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); bool pressed = 0 != (controllerState.ulButtonPressed & mask); - handleButtonEvent(i, pressed, left); + handleButtonEvent(deltaTime, i, pressed, isLeftHand); } + + // process each axis for (uint32_t i = 0; i < vr::k_unControllerStateAxisCount; i++) { - handleAxisEvent(i, controllerState.rAxis[i].x, controllerState.rAxis[i].y, left); + handleAxisEvent(deltaTime, i, controllerState.rAxis[i].x, controllerState.rAxis[i].y, isLeftHand); } } } - - _trackedControllers = numTrackedControllers; } void ViveControllerManager::InputDevice::focusOutEvent() { @@ -284,42 +284,46 @@ void ViveControllerManager::InputDevice::focusOutEvent() { }; // These functions do translation from the Steam IDs to the standard controller IDs -void ViveControllerManager::InputDevice::handleAxisEvent(uint32_t axis, float x, float y, bool left) { +void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand) { //FIX ME? It enters here every frame: probably we want to enter only if an event occurs axis += vr::k_EButton_Axis0; using namespace controller; + if (axis == vr::k_EButton_SteamVR_Touchpad) { - _axisStateMap[left ? LX : RX] = x; - _axisStateMap[left ? LY : RY] = y; + glm::vec2 stick(x, y); + if (isLeftHand) { + stick = _filteredLeftStick.process(deltaTime, stick); + } else { + stick = _filteredRightStick.process(deltaTime, stick); + } + _axisStateMap[isLeftHand ? LX : RX] = stick.x; + _axisStateMap[isLeftHand ? LY : RY] = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { - _axisStateMap[left ? LT : RT] = x; + _axisStateMap[isLeftHand ? LT : RT] = x; } } // These functions do translation from the Steam IDs to the standard controller IDs -void ViveControllerManager::InputDevice::handleButtonEvent(uint32_t button, bool pressed, bool left) { +void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool isLeftHand) { if (!pressed) { return; } + using namespace controller; if (button == vr::k_EButton_ApplicationMenu) { - _buttonPressedMap.insert(left ? controller::LEFT_PRIMARY_THUMB : controller::RIGHT_PRIMARY_THUMB); + _buttonPressedMap.insert(isLeftHand ? LEFT_PRIMARY_THUMB : RIGHT_PRIMARY_THUMB); } else if (button == vr::k_EButton_Grip) { - // Tony says these are harder to reach, so make them the meta buttons - _buttonPressedMap.insert(left ? controller::LB : controller::RB); + _buttonPressedMap.insert(isLeftHand ? LB : RB); } else if (button == vr::k_EButton_SteamVR_Trigger) { - _buttonPressedMap.insert(left ? controller::LT : controller::RT); + _buttonPressedMap.insert(isLeftHand ? LT : RT); } else if (button == vr::k_EButton_SteamVR_Touchpad) { - _buttonPressedMap.insert(left ? controller::LS : controller::RS); - } else if (button == vr::k_EButton_System) { - //FIX ME: not able to ovrewrite the behaviour of this button - _buttonPressedMap.insert(left ? controller::LEFT_SECONDARY_THUMB : controller::RIGHT_SECONDARY_THUMB); + _buttonPressedMap.insert(isLeftHand ? LS : RS); } } -void ViveControllerManager::InputDevice::handlePoseEvent(const controller::InputCalibrationData& inputCalibrationData, +void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, - const vec3& angularVelocity, bool left) { + const vec3& angularVelocity, bool isLeftHand) { // When the sensor-to-world rotation is identity the coordinate axes look like this: // // user @@ -384,8 +388,8 @@ void ViveControllerManager::InputDevice::handlePoseEvent(const controller::Input static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; - auto translationOffset = (left ? leftTranslationOffset : rightTranslationOffset); - auto rotationOffset = (left ? leftRotationOffset : rightRotationOffset); + auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); glm::vec3 position = extractTranslation(mat); glm::quat rotation = glm::normalize(glm::quat_cast(mat)); @@ -399,7 +403,7 @@ void ViveControllerManager::InputDevice::handlePoseEvent(const controller::Input // handle change in velocity due to translationOffset avatarPose.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); avatarPose.angularVelocity = angularVelocity; - _poseStateMap[left ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar); + _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar); } controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableInputs() const { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 51339cd465..480fbfeb90 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -50,7 +50,7 @@ public: private: class InputDevice : public controller::InputDevice { public: - InputDevice(vr::IVRSystem*& hmd) : controller::InputDevice("Vive"), _hmd(hmd) {} + InputDevice(vr::IVRSystem*& system) : controller::InputDevice("Vive"), _system(system) {} private: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; @@ -58,20 +58,46 @@ private: virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; virtual void focusOutEvent() override; - void handleButtonEvent(uint32_t button, bool pressed, bool left); - void handleAxisEvent(uint32_t axis, float x, float y, bool left); - void handlePoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, - const vec3& linearVelocity, const vec3& angularVelocity, bool left); + void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); + void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool isLeftHand); + void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); + void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, + const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); + + class FilteredStick { + public: + glm::vec2 process(float deltaTime, const glm::vec2& stick) { + // Use a timer to prevent the stick going to back to zero. + // This to work around the noisy touch pad that will flash back to zero breifly + const float ZERO_HYSTERESIS_PERIOD = 0.2f; // 200 ms + if (glm::length(stick) == 0.0f) { + if (_timer <= 0.0f) { + return glm::vec2(0.0f, 0.0f); + } else { + _timer -= deltaTime; + return _stick; + } + } else { + _timer = ZERO_HYSTERESIS_PERIOD; + _stick = stick; + return stick; + } + } + protected: + float _timer { 0.0f }; + glm::vec2 _stick { 0.0f, 0.0f }; + }; + + FilteredStick _filteredLeftStick; + FilteredStick _filteredRightStick; int _trackedControllers { 0 }; - vr::IVRSystem*& _hmd; + vr::IVRSystem*& _system; friend class ViveControllerManager; }; void renderHand(const controller::Pose& pose, gpu::Batch& batch, int sign); - - bool _registeredWithInputMapper { false }; bool _modelLoaded { false }; model::Geometry _modelGeometry; @@ -81,8 +107,8 @@ private: int _rightHandRenderID { 0 }; bool _renderControllers { false }; - vr::IVRSystem* _hmd { nullptr }; - std::shared_ptr _inputDevice { std::make_shared(_hmd) }; + vr::IVRSystem* _system { nullptr }; + std::shared_ptr _inputDevice { std::make_shared(_system) }; static const QString NAME; diff --git a/tests/gpu-test/src/unlit.slf b/tests/gpu-test/src/unlit.slf index 77d28aa7e9..f88fcb510b 100644 --- a/tests/gpu-test/src/unlit.slf +++ b/tests/gpu-test/src/unlit.slf @@ -24,5 +24,5 @@ void main(void) { normalize(_normal.xyz), 1.0, _color.rgb, - DEFAULT_SPECULAR, DEFAULT_SHININESS); + DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, DEFAULT_OCCLUSION); } diff --git a/tests/ui/qml/StubMenu.qml b/tests/ui/qml/StubMenu.qml index 63c9b5b3ea..fd0298988a 100644 --- a/tests/ui/qml/StubMenu.qml +++ b/tests/ui/qml/StubMenu.qml @@ -528,10 +528,6 @@ Menu { text: menuOption.turnWithHead; checkable: true } - MenuItem { - text: menuOption.comfortMode; - checkable: true - } MenuItem { text: menuOption.keyboardMotorControl; checkable: true diff --git a/tests/ui/qml/Stubs.qml b/tests/ui/qml/Stubs.qml index 3f60876e81..22b63d3f83 100644 --- a/tests/ui/qml/Stubs.qml +++ b/tests/ui/qml/Stubs.qml @@ -23,6 +23,20 @@ Item { function getUsername() { return "Jherico"; } } + Item { + objectName: "ApplicationCompositor" + property bool reticleOverDesktop: true + } + + Item { + objectName: "Preferences" + // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). + property var categories: [ + "Avatar Basics", "Snapshots", "Scripts", "Privacy", "Level of Detail Tuning", "Avatar Tuning", "Avatar Camera", + "Audio", "Octree", "HMD", "Sixense Controllers", "Graphics" + ] + } + Item { objectName: "ScriptDiscoveryService" //property var scriptsModelFilter: scriptsModel diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 0bdfdb64b9..97f6224670 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -38,6 +38,18 @@ ApplicationWindow { property var tabs: []; property var urls: []; + Button { + // Shows the dialog with preferences sections but not each section's preference items + // because Preferences.preferencesByCategory() method is not stubbed out. + text: "Settings > General..." + property var builder: Component { + GeneralPreferencesDialog { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + Button { text: "Running Scripts" property var builder: Component { diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 19c0626164..dae7b47bba 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -85,9 +85,11 @@ int main(int argc, char *argv[]) { setChild(engine, "offscreenFlags"); setChild(engine, "Account"); + setChild(engine, "ApplicationCompositor"); setChild(engine, "Desktop"); setChild(engine, "ScriptDiscoveryService"); setChild(engine, "MenuHelper"); + setChild(engine, "Preferences"); setChild(engine, "urlHandler"); engine.rootContext()->setContextProperty("DebugQML", true); engine.rootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); diff --git a/tools/scribe/src/TextTemplate.cpp b/tools/scribe/src/TextTemplate.cpp index 3ccb0baea0..741ddb6846 100755 --- a/tools/scribe/src/TextTemplate.cpp +++ b/tools/scribe/src/TextTemplate.cpp @@ -18,6 +18,8 @@ typedef TextTemplate::Block::Pointer BlockPointer; typedef TextTemplate::Config::Pointer ConfigPointer; typedef TextTemplate::Pointer TextTemplatePointer; +const std::string TextTemplate::Tag::NULL_VAR = "_SCRIBE_NULL"; + //----------------------------------------------------------------------------- TextTemplate::Config::Config() : _includes(), @@ -370,7 +372,11 @@ bool TextTemplate::convertExpressionToFuncArguments(String& src, std::vector< St token += c; } else if (c == ',') { if (!token.empty()) { - arguments.push_back(token); + if (token == Tag::NULL_VAR) { + arguments.push_back(Tag::NULL_VAR); + } else { + arguments.push_back(token); + } nbTokens++; } token.clear(); @@ -750,7 +756,9 @@ int TextTemplate::evalBlockGeneration(std::ostream& dst, const BlockPointer& blo paramCache.push_back((*it).second); (*it).second = val; } else { - vars.insert(Vars::value_type(funcBlock->command.arguments[i], val)); + if (val != Tag::NULL_VAR) { + vars.insert(Vars::value_type(funcBlock->command.arguments[i], val)); + } paramCache.push_back(""); } } diff --git a/tools/scribe/src/TextTemplate.h b/tools/scribe/src/TextTemplate.h index a6fd04da5c..44edc23c12 100755 --- a/tools/scribe/src/TextTemplate.h +++ b/tools/scribe/src/TextTemplate.h @@ -42,6 +42,8 @@ public: static const char VAR = '$'; static const char COM = '@'; static const char REM = '!'; + + static const std::string NULL_VAR; }; class Command { diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js b/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js deleted file mode 100644 index a975f74733..0000000000 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js +++ /dev/null @@ -1,182 +0,0 @@ -// 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 -// -(function() { - - var self = this; - - this.preload = function(entityId) { - //print('preload move randomly') - this.isConnected = false; - this.entityId = entityId; - this.updateInterval = 100; - this.posFrame = 0; - this.rotFrame = 0; - this.posInterval = 100; - this.rotInterval = 100; - this.minVelocity = 1; - this.maxVelocity = 5; - this.minAngularVelocity = 0.01; - this.maxAngularVelocity = 0.03; - - this.initialize(entityId); - this.initTimeout = null; - - - var userData = { - ownershipKey: { - owner: MyAvatar.sessionUUID - }, - grabbableKey: { - grabbable: false - } - }; - - Entities.editEntity(entityId, { - userData: JSON.stringify(userData) - }) - } - - this.initialize = function(entityId) { - //print('move randomly should initialize' + entityId) - var properties = Entities.getEntityProperties(entityId); - if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) { - self.initTimeout = Script.setTimeout(function() { - //print('no user data yet, try again in one second') - self.initialize(entityId); - }, 1000) - - } else { - //print('userdata before parse attempt' + properties.userData) - self.userData = null; - try { - self.userData = JSON.parse(properties.userData); - } catch (err) { - //print('error parsing json'); - //print('properties are:' + properties.userData); - return; - } - Script.update.connect(self.update); - this.isConnected = true; - } - } - - this.update = function(deltaTime) { - // print('jbp in update') - var data = Entities.getEntityProperties(self.entityId, 'userData').userData; - var userData; - try { - userData = JSON.parse(data) - } catch (e) { - //print('error parsing json' + data) - return; - }; - - // print('userdata is' + data) - //if the entity doesnt have an owner set yet - if (userData.hasOwnProperty('ownershipKey') !== true) { - //print('no movement owner yet') - return; - } - - //print('owner is:::' + userData.ownershipKey.owner) - //get all the avatars to see if the owner is around - var avatars = AvatarList.getAvatarIdentifiers(); - var ownerIsAround = false; - - //if the current owner is not me... - if (userData.ownershipKey.owner !== MyAvatar.sessionUUID) { - - //look to see if the current owner is around anymore - for (var i = 0; i < avatars.length; i++) { - if (avatars[i] === userData.ownershipKey.owner) { - ownerIsAround = true - //the owner is around - return; - }; - } - - //if the owner is not around, then take ownership - if (ownerIsAround === false) { - //print('taking ownership') - - var userData = { - ownershipKey: { - owner: MyAvatar.sessionUUID - }, - grabbableKey: { - grabbable: false - } - }; - Entities.editEntity(self.entityId, { - userData: JSON.stringify(data) - }) - } - } - //but if the current owner IS me, then move it - else { - //print('jbp im the owner so move it') - self.posFrame++; - self.rotFrame++; - - if (self.posFrame > self.posInterval) { - - self.posInterval = 100 * Math.random() + 300; - self.posFrame = 0; - - var magnitudeV = self.maxVelocity; - var directionV = { - x: Math.random() - 0.5, - y: Math.random() - 0.5, - z: Math.random() - 0.5 - }; - - //print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x); - Entities.editEntity(self.entityId, { - velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)) - - }); - - } - - if (self.rotFrame > self.rotInterval) { - - self.rotInterval = 100 * Math.random() + 250; - self.rotFrame = 0; - - var magnitudeAV = self.maxAngularVelocity; - - var directionAV = { - x: Math.random() - 0.5, - y: Math.random() - 0.5, - z: Math.random() - 0.5 - }; - //print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x); - Entities.editEntity(self.entityId, { - angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) - - }); - - } - - } - - } - - this.unload = function() { - if (this.initTimeout !== null) { - Script.clearTimeout(this.initTimeout); - } - - if (this.isConnected === true) { - Script.update.disconnect(this.update); - } - - } - - - -}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js b/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js index 4136f1f81b..c1187bc6d5 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js @@ -7,7 +7,7 @@ (function() { - var version = 11; + var version = 12; var added = false; this.frame = 0; var utilsScript = Script.resolvePath('utils.js'); @@ -23,22 +23,21 @@ } this.initialize = function(entityId) { - print('JBP nav button should initialize' + entityId) var properties = Entities.getEntityProperties(entityId); if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) { self.initTimeout = Script.setTimeout(function() { - print('JBP no user data yet, try again in one second') + // print(' no user data yet, try again in one second') self.initialize(entityId); }, 1000) } else { - print('JBP userdata before parse attempt' + properties.userData) + // print('userdata before parse attempt' + properties.userData) self.userData = null; try { self.userData = JSON.parse(properties.userData); } catch (err) { - print('JBP error parsing json'); - print('JBP properties are:' + properties.userData); + // print(' error parsing json'); + // print(' properties are:' + properties.userData); return; } @@ -46,9 +45,9 @@ var mySavedSettings = Settings.getValue(entityId); if (mySavedSettings.buttons !== undefined) { - print('JBP preload buttons' + mySavedSettings.buttons) + // print(' preload buttons' + mySavedSettings.buttons) mySavedSettings.buttons.forEach(function(b) { - print('JBP deleting button' + b) + // print(' deleting button' + b) Overlays.deleteOverlay(b); }) Settings.setValue(entityId, '') @@ -56,16 +55,15 @@ self.buttonImageURL = baseURL + "GUI/GUI_" + self.userData.name + ".png?" + version; - print('JBP BUTTON IMAGE URL:' + self.buttonImageURL) + // print(' BUTTON IMAGE URL:' + self.buttonImageURL) if (self.button === undefined) { - // print('NAV NO BUTTON ADDING ONE!!') + // print(' NO BUTTON ADDING ONE!!') self.button = true; self.addButton(); } else { - // print('NAV SELF ALREADY HAS A BUTTON!!') + //print(' SELF ALREADY HAS A BUTTON!!') } - } } diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js b/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js index 6651e435b4..8ee5e0092e 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js @@ -36,8 +36,6 @@ return; } - - self.addButton(); self.buttonShowing = false; self.showDistance = self.userData.showDistance; @@ -51,8 +49,6 @@ }; self.sound = SoundCache.getSound(this.soundURL); - - } this.addButton = function() { diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js b/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js index d8b32ab176..3f6067693c 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js @@ -10,7 +10,7 @@ var self = this; var baseURL = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/"; - var version = 2; + var version = 3; this.preload = function(entityId) { this.soundPlaying = null; this.entityId = entityId; diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js b/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js index da41ec64ba..3fc5acae2a 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js @@ -52,13 +52,7 @@ print("Teleporting to (" + data.location.x + ", " + data.location.y + ", " + data.location.z + ")"); MyAvatar.position = data.location; - - // if (data.hasOwnProperty('entryPoint') && data.hasOwnProperty('target')) { - // this.lookAtTarget(data.entryPoint, data.target); - // } - // else{ - // } } } @@ -103,10 +97,4 @@ } } - this.hoverEnterEntity = function(entityID) { - Entities.editEntity(entityID, { - animationURL: animationURL, - animationSettings: '{ "fps": 24, "firstFrame": 1, "lastFrame": 25, "frameIndex": 1, "running": true, "hold": true }' - }); - } }) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js b/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js index 0ee5b3bf32..1b4c06caaa 100644 --- a/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js +++ b/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js @@ -7,7 +7,7 @@ var soundMap = [{ y: 15850, z: 15850 }, - volume: 0.1, + volume: 0.03, loop: true } }, { @@ -19,7 +19,7 @@ var soundMap = [{ y: 15950, z: 15950 }, - volume: 0.1, + volume: 0.03, loop: true } }, { @@ -31,7 +31,7 @@ var soundMap = [{ y: 15650, z: 15650 }, - volume: 0.1, + volume: 0.03, loop: true } }, { @@ -43,7 +43,7 @@ var soundMap = [{ y: 15750, z: 15750 }, - volume: 0.1, + volume: 0.03, loop: true } } diff --git a/unpublishedScripts/DomainContent/CellScience/importCellScience.js b/unpublishedScripts/DomainContent/CellScience/importCellScience.js index ca0eb21f21..876df8adb1 100644 --- a/unpublishedScripts/DomainContent/CellScience/importCellScience.js +++ b/unpublishedScripts/DomainContent/CellScience/importCellScience.js @@ -5,7 +5,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var version = 1035; +var version = 1112; var cellLayout; var baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/"; @@ -103,9 +103,9 @@ var scenes = [{ instances: [{ model: "Cell", dimensions: { - x: 550, - y: 620, - z: 550 + x: 500, + y: 570, + z: 500 }, offset: { x: 0, @@ -151,294 +151,253 @@ var scenes = [{ skybox: "cosmos_skybox_blurred" }, instances: [{ - model: "translation", - dimensions: { - x: 10, - y: 16, - z: 10 - }, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 300, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.ribosome[1], - location: locations.ribosome[0], - baseURL: baseLocation - }), - script: "zoom.js?" + version, - visible: true - }, { - model: "vesicle", - dimensions: { - x: 60, - y: 60, - z: 60 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1000, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true - }, { //golgi vesicles - model: "vesicle", - dimensions: { - x: 10, - y: 10, - z: 10 - }, - randomSize: 10, - offset: { - x: -319, - y: 66, - z: 976 - }, - radius: 140, - number: 10, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "", - visible: true - }, { //golgi vesicles - model: "vesicle", - dimensions: { - x: 15, - y: 15, - z: 15 - }, - randomSize: 10, - offset: { - x: -319, - y: 66, - z: 976 - }, - radius: 115, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true - }, { - model: "vesicle", - dimensions: { - x: 50, - y: 50, - z: 50 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 600, - number: 15, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "", - visible: true - }, { //outer vesicles - model: "vesicle", - dimensions: { - x: 60, - y: 60, - z: 60 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1600, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "", - visible: true - }, { //outer vesicles - model: "vesicle", - dimensions: { - x: 40, - y: 40, - z: 40 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1400, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true - }, { //outer vesicles - model: "vesicle", - dimensions: { - x: 80, - y: 80, - z: 80 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1800, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true + model: "translation", + dimensions: { + x: 10, + y: 16, + z: 10 }, - // {//wigglies - // model:"wiggly", - // dimensions:{x:320,y:40,z:160}, - // randomSize: 10, - // offset:{x:0,y:0,z:0}, - // radius:1800, - // number:50, - // userData:"", - // script:"moveRandomly", - // visible:true - // }, - //// {//wigglies - // model:"wiggly", - // dimensions:{x:640,y:80,z:320}, - // randomSize: 10, - // offset:{x:0,y:0,z:0}, - // radius:2100, - // number:50, - // userData:"", - // script:"moveRandomly", - // visible:true - // }, - { - model: "hexokinase", - dimensions: { - x: 3, - y: 4, - z: 3 + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 300, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - randomSize: 10, - offset: { - x: 236, - y: 8, - z: 771 + target: locations.ribosome[1], + location: locations.ribosome[0], + baseURL: baseLocation + }), + script: "zoom.js?" + version, + visible: true + }, { + model: "vesicle", + dimensions: { + x: 60, + y: 60, + z: 60 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1000, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { //golgi vesicles + model: "vesicle", + dimensions: { + x: 10, + y: 10, + z: 10 + }, + randomSize: 10, + offset: { + x: -319, + y: 66, + z: 976 + }, + radius: 140, + number: 10, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + script: "", + visible: true + }, { //golgi vesicles + model: "vesicle", + dimensions: { + x: 15, + y: 15, + z: 15 + }, + randomSize: 10, + offset: { + x: -319, + y: 66, + z: 976 + }, + radius: 115, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { + model: "vesicle", + dimensions: { + x: 50, + y: 50, + z: 50 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 600, + number: 15, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + script: "", + visible: true + }, { //outer vesicles + model: "vesicle", + dimensions: { + x: 60, + y: 60, + z: 60 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1600, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + script: "", + visible: true + }, { //outer vesicles + model: "vesicle", + dimensions: { + x: 40, + y: 40, + z: 40 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1400, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { //outer vesicles + model: "vesicle", + dimensions: { + x: 80, + y: 80, + z: 80 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1800, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { + model: "hexokinase", + dimensions: { + x: 3, + y: 4, + z: 3 + }, + randomSize: 10, + offset: { + x: 236, + y: 8, + z: 771 + }, + radius: 80, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - radius: 80, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.hexokinase[1], - location: locations.hexokinase[0], - baseURL: baseLocation - }), - script: "zoom.js?" + version, - visible: true - }, { - model: "pfructo_kinase", - dimensions: { - x: 3, - y: 4, - z: 3 + target: locations.hexokinase[1], + location: locations.hexokinase[0], + baseURL: baseLocation + }), + script: "zoom.js?" + version, + visible: true + }, { + model: "pfructo_kinase", + dimensions: { + x: 3, + y: 4, + z: 3 + }, + randomSize: 10, + offset: { + x: 236, + y: 8, + z: 771 + }, + radius: 60, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - randomSize: 10, - offset: { - x: 236, - y: 8, - z: 771 + target: locations.hexokinase[1], + location: locations.hexokinase[0], + }), + script: "zoom.js?" + version, + visible: true + }, { + model: "glucose_isomerase", + dimensions: { + x: 3, + y: 4, + z: 3 + }, + randomSize: 10, + offset: { + x: 236, + y: 8, + z: 771 + }, + radius: 70, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - radius: 60, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.hexokinase[1], - location: locations.hexokinase[0], - }), - script: "zoom.js?" + version, - visible: true - }, { - model: "glucose_isomerase", - dimensions: { - x: 3, - y: 4, - z: 3 - }, - randomSize: 10, - offset: { - x: 236, - y: 8, - z: 771 - }, - radius: 70, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.hexokinase[1], - location: locations.hexokinase[0], - }), - script: "zoom.js?" + version, - visible: true - } - // { - // model:"NPC", - // dimensions:{x:20,y:20,z:20}, - // randomSize: 10, - // offset:{x:208.593693,y:6.113100222,z:153.3202277}, - // radius:520, - // number:25, - // userData: "", - // script:"", - // visible:true - // } - - - ], + target: locations.hexokinase[1], + location: locations.hexokinase[0], + }), + script: "zoom.js?" + version, + visible: true + }], boundary: { radius: locations.cellLayout[2], center: locations.cellLayout[0], @@ -600,7 +559,7 @@ function ImportScene(scene) { CreateZone(scene); CreateInstances(scene); - CreateBoundary(scene); + // CreateBoundary(scene); // print("done " + scene.name); @@ -609,12 +568,10 @@ function ImportScene(scene) { clearAllNav(); function clearAllNav() { - // print('NAV CLEARING ALL NAV'); var result = Entities.findEntities(MyAvatar.position, 25000); result.forEach(function(r) { var properties = Entities.getEntityProperties(r, "name"); if (properties.name.indexOf('navigation button') > -1) { - // print('NAV DELETING NAV BUTTON AT START:: '+r) Entities.deleteEntity(r); } }) @@ -645,9 +602,6 @@ function createLayoutLights() { } function CreateNavigationButton(scene, number) { - // print('NAV NAVIGATION CREATING NAV!!' +scene.name + " " + number) - - Entities.addEntity({ type: "Box", name: scene.name + " navigation button", @@ -818,7 +772,7 @@ function CreateInstances(scene) { x: 0, y: 0, z: 0 - }, idBounds, 150); + }, idBounds, 150, scene.instances[i]); } //print('SCRIPT AT CREATE ENTITY: ' + script) @@ -831,6 +785,7 @@ function CreateInstances(scene) { function CreateIdentification(name, position, rotation, dimensions, showDistance) { //print ("creating ID for " + name); + Entities.addEntity({ type: "Sphere", name: "ID for " + name, @@ -9045,4 +9000,9 @@ createLayoutLights(); Script.scriptEnding.connect(function() { Entities.addingEntity.disconnect(makeUngrabbable); -}); \ No newline at end of file +}); + +Script.setTimeout(function() { + print('JBP stopping cell science import'); + Script.stop(); +}, 30000) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js b/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js index 672ec1fd92..59577d49ba 100644 --- a/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js +++ b/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js @@ -18,8 +18,6 @@ if (USE_LOCAL_HOST === true) { var USE_LOCAL_HOST = false; -Agent.isAvatar = true; - EntityViewer.setPosition({ x: 3000, y: 13500, diff --git a/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js b/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js new file mode 100644 index 0000000000..cf35e081e0 --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js @@ -0,0 +1,103 @@ +// 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 +// + +var basePosition = { + x: 3000, + y: 13500, + z: 3000 +}; + +var initialized = false; + +EntityViewer.setPosition(basePosition); +EntityViewer.setKeyholeRadius(60000); +var octreeQueryInterval = Script.setInterval(function() { + EntityViewer.queryOctree(); +}, 200); + +var THROTTLE = true; +var THROTTLE_RATE = 5000; + +var sinceLastUpdate = 0; + +//print('cells script') + +function findCells() { + var results = Entities.findEntities(basePosition, 60000); + + if (results.length === 0) { + // print('no entities found') + return; + } + + results.forEach(function(v) { + var name = Entities.getEntityProperties(v, 'name').name; + // print('name is:: ' + name) + if (name === 'Cell') { + // print('found a cell!!' + v) + Script.setTimeout(function() { + moveCell(v); + }, Math.random() * THROTTLE_RATE); + } + }); +} + + +var minAngularVelocity = 0.01; +var maxAngularVelocity = 0.03; + +function moveCell(entityId) { + // print('moving a cell! ' + entityId) + + var magnitudeAV = maxAngularVelocity; + + var directionAV = { + x: Math.random() - 0.5, + y: Math.random() - 0.5, + z: Math.random() - 0.5 + }; + // print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x); + Entities.editEntity(entityId, { + angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) + }); + +} + +function update(deltaTime) { + + // print('deltaTime',deltaTime) + if (!initialized) { + print("checking for servers..."); + if (Entities.serversExist() && Entities.canRez()) { + print("servers exist -- makeAll..."); + Entities.setPacketsPerSecond(6000); + print("PPS:" + Entities.getPacketsPerSecond()); + initialized = true; + } + return; + } + + if (THROTTLE === true) { + sinceLastUpdate = sinceLastUpdate + deltaTime * 1000; + if (sinceLastUpdate > THROTTLE_RATE) { + // print('SHOULD FIND CELLS!!!') + sinceLastUpdate = 0; + findCells(); + } else { + // print('returning in update ' + sinceLastUpdate) + return; + } + } + +} + +function unload() { + Script.update.disconnect(update); +} + +Script.update.connect(update); +Script.scriptEnding.connect(unload); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js b/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js new file mode 100644 index 0000000000..922f0d94cf --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js @@ -0,0 +1,108 @@ +// 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 +// + +var basePosition = { + x: 3000, + y: 13500, + z: 3000 +}; + +var initialized = false; + +EntityViewer.setPosition(basePosition); +EntityViewer.setKeyholeRadius(60000); +var octreeQueryInterval = Script.setInterval(function() { + EntityViewer.queryOctree(); +}, 200); + +var THROTTLE = true; +var THROTTLE_RATE = 5000; + +var sinceLastUpdate = 0; + +//print('vesicle script') + +function findVesicles() { + var results = Entities.findEntities(basePosition, 60000); + + if (results.length === 0) { + // print('no entities found'); + return; + } + + results.forEach(function(v) { + var name = Entities.getEntityProperties(v, 'name').name; + if (name === 'vesicle') { + //print('found a vesicle!!' + v) + Script.setTimeout(function() { + moveVesicle(v); + }, Math.random() * THROTTLE_RATE); + } + }); +} + +var minVelocity = 1; +var maxVelocity = 5; +var minAngularVelocity = 0.01; +var maxAngularVelocity = 0.03; + +function moveVesicle(entityId) { + // print('moving a vesicle! ' + entityId) + var magnitudeV = maxVelocity; + var directionV = { + x: Math.random() - 0.5, + y: Math.random() - 0.5, + z: Math.random() - 0.5 + }; + + // print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x); + + var magnitudeAV = maxAngularVelocity; + + var directionAV = { + x: Math.random() - 0.5, + y: Math.random() - 0.5, + z: Math.random() - 0.5 + }; + // print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x); + Entities.editEntity(entityId, { + velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)), + angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) + }); + +} + +function update(deltaTime) { + if (!initialized) { + print("checking for servers..."); + if (Entities.serversExist() && Entities.canRez()) { + print("servers exist -- makeAll..."); + Entities.setPacketsPerSecond(6000); + print("PPS:" + Entities.getPacketsPerSecond()); + initialized = true; + } + return; + } + + if (THROTTLE === true) { + sinceLastUpdate = sinceLastUpdate + deltaTime * 1000; + if (sinceLastUpdate > THROTTLE_RATE) { + sinceLastUpdate = 0; + findVesicles(); + } else { + return; + } + } + +} + +function unload() { + Script.update.disconnect(update); +} + +Script.update.connect(update); +Script.scriptEnding.connect(unload); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js b/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js index 557861ba30..f155c28b41 100644 --- a/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js +++ b/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js @@ -96,6 +96,9 @@ if (this.ballLocked === true) { return; } + if(this.ball!==null){ + Entities.deleteEntity(this.ball); + } var properties = { name: 'Hifi Tilt Maze Ball', diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 4a64b34510..08c5b5857a 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -973,6 +973,8 @@ y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1001,6 +1003,8 @@ y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1074,6 +1078,8 @@ y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1103,6 +1109,8 @@ y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1131,6 +1139,8 @@ y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1241,8 +1251,9 @@ } function createPingPongBallGun() { - var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'; - var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj'; + + var MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.fbx'; + var COLLISION_HULL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.obj'; var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav'; var position = { x: 548.6, @@ -1267,9 +1278,9 @@ }, restitution: 0, dimensions: { - x: 0.08, - y: 0.21, - z: 0.47 + x: 0.125, + y: 0.3875, + z: 0.9931 }, dynamic: true, collisionSoundURL: COLLISION_SOUND_URL, diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index c5669b9ac0..cbcee30b0b 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -720,6 +720,7 @@ MasterReset = function() { function createTargets() { + var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/target.fbx'; var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/target_collision_hull.obj'; @@ -960,6 +961,8 @@ MasterReset = function() { y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -988,6 +991,8 @@ MasterReset = function() { y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1061,6 +1066,8 @@ MasterReset = function() { y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1090,6 +1097,8 @@ MasterReset = function() { y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1118,6 +1127,8 @@ MasterReset = function() { y: 2.545, z: 2.545 }, + intensity: 1.0, + falloffRadius: 0.3, cutoff: 90, color: { red: 217, @@ -1228,8 +1239,9 @@ MasterReset = function() { } function createPingPongBallGun() { - var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'; - var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj'; + var MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.fbx'; + var COLLISION_HULL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.obj'; + var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav'; var position = { x: 548.6, @@ -1254,9 +1266,9 @@ MasterReset = function() { }, restitution: 0, dimensions: { - x: 0.08, - y: 0.21, - z: 0.47 + x: 0.125, + y: 0.3875, + z: 0.9931 }, dynamic: true, collisionSoundURL: COLLISION_SOUND_URL,