diff --git a/.gitignore b/.gitignore index 3b44b99e68..0a2bcc6689 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ CMakeFiles/ CMakeScripts/ cmake_install.cmake build*/ +ext/ Makefile *.user @@ -32,10 +33,6 @@ DerivedData interface/external/*/* !interface/external/*/readme.txt -# ignore interface optional resources -interface/resources/visage/* -!interface/resources/visage/tracker.cfg - # Ignore interfaceCache for Linux users interface/interfaceCache/ @@ -46,4 +43,6 @@ libraries/audio-client/external/*/* gvr-interface/assets/oculussig* gvr-interface/libs/* -TAGS \ No newline at end of file +# ignore files for various dev environments +TAGS +*.swp diff --git a/BUILD.md b/BUILD.md index fe0459e7db..a5ee32262f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,9 +1,9 @@ ###Dependencies * [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.12.2 -* [Qt](http://qt-project.org/downloads) ~> 5.3.2 -* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g - * IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability. +* [Qt](http://www.qt.io/download-open-source) ~> 5.4.1 +* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1m + * IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) ####CMake External Project Dependencies @@ -19,9 +19,9 @@ The following external projects are optional dependencies. You can indicate to C * [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3 * Enables game controller support in Interface -The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build-ext` directory in each of the subfolders for each external project. +The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build/ext` folder in each of the subfolders for each external project. -These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build-ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build-ext` folder. +These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build/ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build/ext` folder. If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DGET_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. @@ -37,12 +37,12 @@ Hifi uses CMake to generate build files and project files for your platform. ####Qt In order for CMake to find the Qt5 find modules, you will need to set an ENV variable pointing to your Qt installation. -For example, a Qt5 5.3.2 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). +For example, a Qt5 5.4.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.3.2/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.3.2/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.4.1/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.4.1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake ####Generating build files @@ -57,7 +57,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.3.2/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.4.1/lib/cmake ####Finding Dependencies diff --git a/BUILD_OSX.md b/BUILD_OSX.md index c199fe4c73..2598507c90 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -10,7 +10,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies We have a [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas) that you can use/tap to install some of the dependencies. In the code block above qt5 is installed from a formula in this repository. -*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.3.x stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.* +*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.4.x stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.* ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 39252ec4f2..169077ed78 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -29,12 +29,12 @@ NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit * [Download the online installer](http://qt-project.org/downloads) * When it asks you to select components, ONLY select the following: - * Qt > Qt 5.3.2 > **msvc2013 32-bit OpenGL** + * Qt > Qt 5.4.1 > **msvc2013 32-bit OpenGL** -* [Download the offline installer](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe) +* [Download the offline installer](http://download.qt.io/official_releases/qt/5.4/5.4.1/qt-opensource-windows-x86-msvc2013_opengl-5.4.1.exe) Once Qt is installed, you need to manually configure the following: -* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.3.2\msvc2013_opengl\lib\cmake` directory. +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.4.1\msvc2013_opengl\lib\cmake` directory. * You can set an environment variable from Control Panel > System > Advanced System Settings > Environment Variables > New ###External Libraries @@ -71,7 +71,7 @@ Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll To prevent these problems, install OpenSSL yourself. Download the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html): * Visual C++ 2008 Redistributables -* Win32 OpenSSL v1.0.1L +* Win32 OpenSSL v1.0.1m Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version. diff --git a/CMakeLists.txt b/CMakeLists.txt index 81d532fbe2..347341efa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,10 +45,11 @@ if (WIN32) # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE") -elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic") - #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing -ggdb") +else () + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter") + if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb") + endif () endif(WIN32) if (NOT MSVC12) @@ -92,8 +93,17 @@ else () if (NOT QT_CMAKE_PREFIX_PATH) set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) endif () + if (NOT QT_CMAKE_PREFIX_PATH) + get_filename_component(QT_CMAKE_PREFIX_PATH "${Qt5_DIR}/.." REALPATH) + endif () endif () +if (WIN32) + if (NOT EXISTS ${QT_CMAKE_PREFIX_PATH}) + message(FATAL_ERROR "Could not determine QT_CMAKE_PREFIX_PATH.") + endif () +endif() + # figure out where the qt dir is get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) @@ -168,6 +178,7 @@ option(GET_SOXR "Get Soxr library automatically as external project" 1) option(GET_TBB "Get Threading Building Blocks library automatically as external project" 1) option(GET_LIBOVR "Get LibOVR library automatically as external project" 1) option(USE_NSIGHT "Attempt to find the nSight libraries" 1) +option(GET_VHACD "Get V-HACD library automatically as external project" 1) if (WIN32) option(GET_GLEW "Get GLEW library automatically as external project" 1) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index a0da273462..594805c7c2 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -114,7 +114,23 @@ void AssignmentClient::stopAssignmentClient() { qDebug() << "Exiting."; _requestTimer.stop(); _statsTimerACM.stop(); - QCoreApplication::quit(); + if (_currentAssignment) { + _currentAssignment->aboutToQuit(); + QThread* currentAssignmentThread = _currentAssignment->thread(); + currentAssignmentThread->quit(); + currentAssignmentThread->wait(); + } +} + + +void AssignmentClient::aboutToQuit() { + stopAssignmentClient(); + // clear the log handler so that Qt doesn't call the destructor on LogHandler + qInstallMessageHandler(0); + // clear out pointer to the assignment so the destructor gets called. if we don't do this here, + // it will get destroyed along with all the other "static" stuff. various static member variables + // will be destroyed first and things go wrong. + _currentAssignment.clear(); } @@ -197,6 +213,7 @@ void AssignmentClient::readPendingDatagrams() { // start the deployed assignment AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this); + workerThread->setObjectName("worker"); connect(workerThread, &QThread::started, _currentAssignment.data(), &ThreadedAssignment::run); connect(_currentAssignment.data(), &ThreadedAssignment::finished, workerThread, &QThread::quit); diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 08673ab04c..1ffb862dd3 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -34,6 +34,9 @@ private slots: void sendStatsPacketToACM(); void stopAssignmentClient(); +public slots: + void aboutToQuit(); + private: void setUpStatsToMonitor(int ppid); Assignment _requestAssignment; diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 17f2eac70d..2de349ca4e 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -180,14 +181,19 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : } } + QThread::currentThread()->setObjectName("main thread"); + + DependencyManager::registerInheritance(); if (numForks || minForks || maxForks) { AssignmentClientMonitor monitor(numForks, minForks, maxForks, requestAssignmentType, assignmentPool, walletUUID, assignmentServerHostname, assignmentServerPort); + connect(this, &QCoreApplication::aboutToQuit, &monitor, &AssignmentClientMonitor::aboutToQuit); exec(); } else { AssignmentClient client(ppid, requestAssignmentType, assignmentPool, walletUUID, assignmentServerHostname, assignmentServerPort); + connect(this, &QCoreApplication::aboutToQuit, &client, &AssignmentClient::aboutToQuit); exec(); } } diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index d591087acd..6414f33644 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -22,6 +22,7 @@ const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor"; +const int WAIT_FOR_CHILD_MSECS = 500; AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks, @@ -69,6 +70,17 @@ AssignmentClientMonitor::~AssignmentClientMonitor() { stopChildProcesses(); } +void AssignmentClientMonitor::waitOnChildren(int msecs) { + QMutableListIterator i(_childProcesses); + while (i.hasNext()) { + QProcess* childProcess = i.next(); + bool finished = childProcess->waitForFinished(msecs); + if (finished) { + i.remove(); + } + } +} + void AssignmentClientMonitor::stopChildProcesses() { auto nodeList = DependencyManager::get(); @@ -78,11 +90,41 @@ void AssignmentClientMonitor::stopChildProcesses() { QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode); nodeList->writeUnverifiedDatagram(diePacket, *node->getActiveSocket()); }); + + // try to give all the children time to shutdown + waitOnChildren(WAIT_FOR_CHILD_MSECS); + + // ask more firmly + QMutableListIterator i(_childProcesses); + while (i.hasNext()) { + QProcess* childProcess = i.next(); + childProcess->terminate(); + } + + // try to give all the children time to shutdown + waitOnChildren(WAIT_FOR_CHILD_MSECS); + + // ask even more firmly + QMutableListIterator j(_childProcesses); + while (j.hasNext()) { + QProcess* childProcess = j.next(); + childProcess->kill(); + } + + waitOnChildren(WAIT_FOR_CHILD_MSECS); +} + +void AssignmentClientMonitor::aboutToQuit() { + stopChildProcesses(); + // clear the log handler so that Qt doesn't call the destructor on LogHandler + qInstallMessageHandler(0); } void AssignmentClientMonitor::spawnChildClient() { QProcess *assignmentClient = new QProcess(this); + _childProcesses.append(assignmentClient); + // unparse the parts of the command-line that the child cares about QStringList _childArguments; if (_assignmentPool != "") { @@ -119,8 +161,6 @@ void AssignmentClientMonitor::spawnChildClient() { qDebug() << "Spawned a child client with PID" << assignmentClient->pid(); } - - void AssignmentClientMonitor::checkSpares() { auto nodeList = DependencyManager::get(); QUuid aSpareId = ""; @@ -156,6 +196,8 @@ void AssignmentClientMonitor::checkSpares() { nodeList->writeUnverifiedDatagram(diePacket, childNode); } } + + waitOnChildren(0); } diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index 996220b1b4..e0129bd9b9 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -32,12 +32,16 @@ public: QString assignmentPool, QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort); ~AssignmentClientMonitor(); - + + void waitOnChildren(int msecs); void stopChildProcesses(); private slots: void readPendingDatagrams(); void checkSpares(); +public slots: + void aboutToQuit(); + private: void spawnChildClient(); QTimer _checkSparesTimer; // every few seconds see if it need fewer or more spare children @@ -52,6 +56,7 @@ private: QString _assignmentServerHostname; quint16 _assignmentServerPort; + QList _childProcesses; }; #endif // hifi_AssignmentClientMonitor_h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index dd566bc40b..06e6f77f69 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -549,14 +549,18 @@ void AudioMixer::readPendingDatagram(const QByteArray& receivedPacket, const Hif nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); } else if (mixerPacketType == PacketTypeMuteEnvironment) { - QByteArray packet = receivedPacket; - populatePacketHeader(packet, PacketTypeMuteEnvironment); - - nodeList->eachNode([&](const SharedNodePointer& node){ - if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) { - nodeList->writeDatagram(packet, packet.size(), node); - } - }); + SharedNodePointer sendingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (sendingNode->getCanAdjustLocks()) { + QByteArray packet = receivedPacket; + populatePacketHeader(packet, PacketTypeMuteEnvironment); + + nodeList->eachNode([&](const SharedNodePointer& node){ + if (node->getType() == NodeType::Agent && node->getActiveSocket() && + node->getLinkedData() && node != sendingNode) { + nodeList->writeDatagram(packet, packet.size(), node); + } + }); + } } else { // let processNodeData handle it. nodeList->processNodeData(senderSockAddr, receivedPacket); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index dae6af3fc5..6c8ba91f1b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -45,6 +45,9 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) : } AvatarMixer::~AvatarMixer() { + if (_broadcastTimer) { + _broadcastTimer->deleteLater(); + } _broadcastThread.quit(); _broadcastThread.wait(); } @@ -343,13 +346,13 @@ void AvatarMixer::run() { nodeList->linkedDataCreateCallback = attachAvatarDataToNode; // setup the timer that will be fired on the broadcast thread - QTimer* broadcastTimer = new QTimer(); - broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); - broadcastTimer->moveToThread(&_broadcastThread); + _broadcastTimer = new QTimer(); + _broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); + _broadcastTimer->moveToThread(&_broadcastThread); // connect appropriate signals and slots - connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection); - connect(&_broadcastThread, SIGNAL(started()), broadcastTimer, SLOT(start())); + connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection); + connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start())); // start the broadcastThread _broadcastThread.start(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index a69019427b..4746f02d14 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -47,6 +47,8 @@ private: int _numStatFrames; int _sumBillboardPackets; int _sumIdentityPackets; + + QTimer* _broadcastTimer = nullptr; }; #endif // hifi_AvatarMixer_h diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 85d0a7414c..bb5042f4b4 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -27,6 +27,11 @@ EntityServer::EntityServer(const QByteArray& packet) } EntityServer::~EntityServer() { + if (_pruneDeletedEntitiesTimer) { + _pruneDeletedEntitiesTimer->stop(); + _pruneDeletedEntitiesTimer->deleteLater(); + } + EntityTree* tree = (EntityTree*)_tree; tree->removeNewlyCreatedHook(this); } @@ -48,10 +53,10 @@ Octree* EntityServer::createTree() { } void EntityServer::beforeRun() { - QTimer* pruneDeletedEntitiesTimer = new QTimer(this); - connect(pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities())); + _pruneDeletedEntitiesTimer = new QTimer(); + connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities())); const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second - pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS); + _pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS); } void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) { diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index d8c2e39f3b..9edec7b704 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -51,6 +51,7 @@ protected: private: EntitySimulation* _entitySimulation; + QTimer* _pruneDeletedEntitiesTimer = nullptr; }; #endif // hifi_EntityServer_h diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.h b/assignment-client/src/octree/OctreeInboundPacketProcessor.h index 8f07f9d566..156e09b493 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.h +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.h @@ -74,7 +74,7 @@ public: NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; } - void shuttingDown() { _shuttingDown = true;} + virtual void terminating() { _shuttingDown = true; ReceivedPacketProcessor::terminating(); } protected: diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 266183745f..f6f1c486b5 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -266,16 +266,19 @@ OctreeServer::~OctreeServer() { } if (_jurisdictionSender) { + _jurisdictionSender->terminating(); _jurisdictionSender->terminate(); _jurisdictionSender->deleteLater(); } if (_octreeInboundPacketProcessor) { + _octreeInboundPacketProcessor->terminating(); _octreeInboundPacketProcessor->terminate(); _octreeInboundPacketProcessor->deleteLater(); } if (_persistThread) { + _persistThread->terminating(); _persistThread->terminate(); _persistThread->deleteLater(); } @@ -1095,8 +1098,6 @@ void OctreeServer::readConfiguration() { } void OctreeServer::run() { - qInstallMessageHandler(LogHandler::verboseMessageHandler); - _safeServerName = getMyServerName(); // Before we do anything else, create our tree... @@ -1219,8 +1220,15 @@ void OctreeServer::forceNodeShutdown(SharedNodePointer node) { void OctreeServer::aboutToFinish() { qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish..."; qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down..."; - _octreeInboundPacketProcessor->shuttingDown(); - + + if (_octreeInboundPacketProcessor) { + _octreeInboundPacketProcessor->terminating(); + } + + if (_jurisdictionSender) { + _jurisdictionSender->terminating(); + } + DependencyManager::get()->eachNode([this](const SharedNodePointer& node) { qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node; forceNodeShutdown(node); @@ -1228,6 +1236,7 @@ void OctreeServer::aboutToFinish() { if (_persistThread) { _persistThread->aboutToFinish(); + _persistThread->terminating(); } qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish..."; diff --git a/cmake/externals/vhacd/CMakeLists.txt b/cmake/externals/vhacd/CMakeLists.txt new file mode 100644 index 0000000000..3bd17937d7 --- /dev/null +++ b/cmake/externals/vhacd/CMakeLists.txt @@ -0,0 +1,31 @@ +set(EXTERNAL_NAME vhacd) + +if (ANDROID) + set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") +endif () + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://hifi-public.s3.amazonaws.com/dependencies/v-hacd-master.zip + URL_MD5 3bfc94f8dd3dfbfe8f4dc088f4820b3e + CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= + BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/VHACD_LIB.lib CACHE FILEPATH "Path to V-HACD debug library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/Release/VHACD_LIB.lib CACHE FILEPATH "Path to V-HACD release library") +else () + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to V-HACD debug library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libVHACD.a CACHE FILEPATH "Path to V-HACD release library") +endif () + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to V-HACD include directory") diff --git a/cmake/macros/AddResourcesToOSXBundle.cmake b/cmake/macros/AddResourcesToOSXBundle.cmake new file mode 100644 index 0000000000..113f751731 --- /dev/null +++ b/cmake/macros/AddResourcesToOSXBundle.cmake @@ -0,0 +1,40 @@ +# +# AddResourcesToOSXBundle.cmake +# cmake/macros +# +# Created by Stephen Birarda on 04/27/15. +# Copyright 2015 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# + +macro(_RECURSIVELY_SET_PACKAGE_LOCATION _PATH) + # enumerate the items + foreach(_ITEM ${ARGN}) + + if (IS_DIRECTORY "${_ITEM}") + # recurse into the directory and check for more resources + file(GLOB _DIR_CONTENTS "${_ITEM}/*") + + # figure out the path for this directory and then call ourselves to recurse into it + get_filename_component(_ITEM_PATH ${_ITEM} NAME) + _recursively_set_package_location("${_PATH}/${_ITEM_PATH}" ${_DIR_CONTENTS}) + else () + # add any files in the root to the resources folder directly + SET_SOURCE_FILES_PROPERTIES(${_ITEM} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources${_PATH}") + list(APPEND DISCOVERED_RESOURCES ${_ITEM}) + endif () + + endforeach() +endmacro(_RECURSIVELY_SET_PACKAGE_LOCATION _PATH) + +macro(ADD_RESOURCES_TO_OS_X_BUNDLE _RSRC_FOLDER) + + # GLOB the resource directory + file(GLOB _ROOT_ITEMS "${_RSRC_FOLDER}/*") + + # recursively enumerate from the root items + _recursively_set_package_location("" ${_ROOT_ITEMS}) + +endmacro(ADD_RESOURCES_TO_OS_X_BUNDLE _RSRC_FOLDER) diff --git a/cmake/macros/SetupExternalsBinaryDir.cmake b/cmake/macros/SetupExternalsBinaryDir.cmake index 04bd00643c..a118dcf543 100644 --- a/cmake/macros/SetupExternalsBinaryDir.cmake +++ b/cmake/macros/SetupExternalsBinaryDir.cmake @@ -22,11 +22,11 @@ macro(SETUP_EXTERNALS_BINARY_DIR) endif () endif () - set(EXTERNALS_BINARY_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/build-ext") + set(EXTERNALS_BINARY_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/ext") if (ANDROID) set(EXTERNALS_BINARY_DIR "${EXTERNALS_BINARY_ROOT_DIR}/android/${CMAKE_GENERATOR_FOLDER_NAME}") else () set(EXTERNALS_BINARY_DIR "${EXTERNALS_BINARY_ROOT_DIR}/${CMAKE_GENERATOR_FOLDER_NAME}") endif () -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/modules/FindVHACD.cmake b/cmake/modules/FindVHACD.cmake index 03e30b41d4..8572387f91 100644 --- a/cmake/modules/FindVHACD.cmake +++ b/cmake/modules/FindVHACD.cmake @@ -19,40 +19,16 @@ include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("vhacd") -macro(_FIND_VHACD_LIBRARY _var) - set(_${_var}_NAMES ${ARGN}) - find_library(${_var}_LIBRARY_RELEASE - NAMES ${_${_var}_NAMES} - HINTS - ${VHACD_SEARCH_DIRS} - $ENV{VHACD_ROOT_DIR} - PATH_SUFFIXES lib lib/Release - ) - - find_library(${_var}_LIBRARY_DEBUG - NAMES ${_${_var}_NAMES} - HINTS - ${VHACD_SEARCH_DIRS} - $ENV{VHACD_ROOT_DIR} - PATH_SUFFIXES lib lib/Debug - ) - - select_library_configurations(${_var}) - - mark_as_advanced(${_var}_LIBRARY) - mark_as_advanced(${_var}_LIBRARY) -endmacro() - +find_path(VHACD_INCLUDE_DIRS VHACD.h PATH_SUFFIXES include HINTS ${VHACD_SEARCH_DIRS}) -find_path(VHACD_INCLUDE_DIRS VHACD.h PATH_SUFFIXES include HINTS ${VHACD_SEARCH_DIRS} $ENV{VHACD_ROOT_DIR}) -if(NOT WIN32) -_FIND_VHACD_LIBRARY(VHACD libVHACD.a) -else() -_FIND_VHACD_LIBRARY(VHACD VHACD_LIB) -endif() +find_library(VHACD_LIBRARY_DEBUG NAMES VHACD VHACD_LIB PATH_SUFFIXES lib/Debug HINTS ${VHACD_SEARCH_DIRS}) +find_library(VHACD_LIBRARY_RELEASE NAMES VHACD VHACD_LIB PATH_SUFFIXES lib/Release lib HINTS ${VHACD_SEARCH_DIRS}) + +include(SelectLibraryConfigurations) +select_library_configurations(VHACD) + set(VHACD_LIBRARIES ${VHACD_LIBRARY}) -include(FindPackageHandleStandardArgs) find_package_handle_standard_args(VHACD "Could NOT find VHACD, try to set the path to VHACD root folder in the system variable VHACD_ROOT_DIR or create a directory vhacd in HIFI_LIB_DIR and paste the necessary files there" VHACD_INCLUDE_DIRS VHACD_LIBRARIES) diff --git a/cmake/modules/FindVisage.cmake b/cmake/modules/FindVisage.cmake deleted file mode 100644 index 2b288f4681..0000000000 --- a/cmake/modules/FindVisage.cmake +++ /dev/null @@ -1,68 +0,0 @@ -# -# FindVisage.cmake -# -# Try to find the Visage controller library -# -# You must provide a VISAGE_ROOT_DIR which contains lib and include directories -# -# Once done this will define -# -# VISAGE_FOUND - system found Visage -# VISAGE_INCLUDE_DIRS - the Visage include directory -# VISAGE_LIBRARIES - Link this to use Visage -# -# Created on 2/11/2014 by Andrzej Kapolka -# Copyright 2014 High Fidelity, Inc. -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# - -include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") -hifi_library_search_hints("visage") - -find_path(VISAGE_BASE_INCLUDE_DIR VisageTracker2.h PATH_SUFFIXES include HINTS ${VISAGE_SEARCH_DIRS}) - -if (APPLE) - find_path(VISAGE_XML_INCLUDE_DIR libxml/xmlreader.h HINTS /usr/include/libxml2 ${VISAGE_SEARCH_DIRS}) - find_path(VISAGE_OPENCV_INCLUDE_DIR cv.h PATH_SUFFIXES dependencies/OpenCV_MacOSX/include HINTS ${VISAGE_SEARCH_DIRS}) - find_path(VISAGE_OPENCV2_INCLUDE_DIR opencv.hpp PATH_SUFFIXES dependencies/OpenCV_MacOSX/include/opencv2 HINTS ${VISAGE_SEARCH_DIRS}) - - find_library(VISAGE_CORE_LIBRARY NAME vscore PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS}) - find_library(VISAGE_VISION_LIBRARY NAME vsvision PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS}) - find_library(VISAGE_OPENCV_LIBRARY NAME OpenCV PATH_SUFFIXES dependencies/OpenCV_MacOSX/lib HINTS ${VISAGE_SEARCH_DIRS}) - - find_library(CoreVideo CoreVideo) - find_library(QTKit QTKit) - find_library(IOKit IOKit) - -elseif (WIN32) - find_path(VISAGE_XML_INCLUDE_DIR libxml/xmlreader.h PATH_SUFFIXES dependencies/libxml2/include HINTS ${VISAGE_SEARCH_DIRS}) - find_path(VISAGE_OPENCV_INCLUDE_DIR opencv/cv.h PATH_SUFFIXES dependencies/OpenCV/include HINTS ${VISAGE_SEARCH_DIRS}) - find_path(VISAGE_OPENCV2_INCLUDE_DIR cv.h PATH_SUFFIXES dependencies/OpenCV/include/opencv HINTS ${VISAGE_SEARCH_DIRS}) - - find_library(VISAGE_CORE_LIBRARY NAME vscore PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS}) - find_library(VISAGE_VISION_LIBRARY NAME vsvision PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS}) - find_library(VISAGE_OPENCV_LIBRARY NAME opencv_core243 PATH_SUFFIXES dependencies/OpenCV/lib HINTS ${VISAGE_SEARCH_DIRS}) -endif () - -include(FindPackageHandleStandardArgs) - -list(APPEND VISAGE_ARGS_LIST VISAGE_BASE_INCLUDE_DIR VISAGE_XML_INCLUDE_DIR - VISAGE_OPENCV_INCLUDE_DIR VISAGE_OPENCV2_INCLUDE_DIR - VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY) - -if (APPLE) - list(APPEND VISAGE_ARGS_LIST CoreVideo QTKit IOKit) -endif () - -find_package_handle_standard_args(Visage DEFAULT_MSG ${VISAGE_ARGS_LIST}) - -set(VISAGE_INCLUDE_DIRS "${VISAGE_XML_INCLUDE_DIR}" "${VISAGE_OPENCV_INCLUDE_DIR}" "${VISAGE_OPENCV2_INCLUDE_DIR}" "${VISAGE_BASE_INCLUDE_DIR}") -set(VISAGE_LIBRARIES "${VISAGE_CORE_LIBRARY}" "${VISAGE_VISION_LIBRARY}") - -if (APPLE) - list(APPEND VISAGE_LIBRARIES "${CoreVideo}" "${QTKit}" "${IOKit}") -endif () - -mark_as_advanced(VISAGE_INCLUDE_DIRS VISAGE_LIBRARIES) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9ce15b7507..1ddf1cb7db 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -302,6 +302,9 @@ bool DomainServer::didSetupAccountManagerWithAccessToken() { << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; return false; } + } else { + qDebug() << "Using access token from DOMAIN_SERVER_ACCESS_TOKEN in env. This overrides any access token present" + << " in the user or master config."; } // give this access token to the AccountManager @@ -501,7 +504,7 @@ void DomainServer::populateStaticScriptedAssignmentsFromSettings() { Assignment::AgentType, scriptPool); scriptAssignment->setPayload(scriptURL.toUtf8()); - + // add it to static hash so we know we have to keep giving it back out addStaticAssignmentToAssignmentHash(scriptAssignment); } @@ -672,6 +675,10 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID()); + // always allow assignment clients to create and destroy entities + newNode->setCanAdjustLocks(true); + newNode->setCanRez(true); + // now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data delete pendingAssigneeData; } @@ -2116,10 +2123,10 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig Assignment* assignment = sharedAssignment->data(); bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes; bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType(); - bool nietherHasPool = assignment->getPool().isEmpty() && requestAssignment.getPool().isEmpty(); + bool neitherHasPool = assignment->getPool().isEmpty() && requestAssignment.getPool().isEmpty(); bool assignmentPoolsMatch = assignment->getPool() == requestAssignment.getPool(); - if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) { + if ((requestIsAllTypes || assignmentTypesMatch) && (neitherHasPool || assignmentPoolsMatch)) { // remove the assignment from the queue SharedAssignmentPointer deployableAssignment = _unfulfilledAssignments.takeAt(sharedAssignment diff --git a/domain-server/src/DomainServerWebSessionData.cpp b/domain-server/src/DomainServerWebSessionData.cpp index ee32a17570..0a921d65c3 100644 --- a/domain-server/src/DomainServerWebSessionData.cpp +++ b/domain-server/src/DomainServerWebSessionData.cpp @@ -33,7 +33,7 @@ DomainServerWebSessionData::DomainServerWebSessionData(const QJsonObject& userOb } } -DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) { +DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) : QObject() { _username = otherSessionData._username; _roles = otherSessionData._roles; } diff --git a/examples/acScripts/rain.js b/examples/acScripts/rain.js new file mode 100644 index 0000000000..ee8a1e19a6 --- /dev/null +++ b/examples/acScripts/rain.js @@ -0,0 +1,163 @@ +// +// rain.js +// examples +// +// Created by David Rowe on 9 Apr 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var RainSquall = function (properties) { + var // Properties + squallOrigin, + squallRadius, + dropsPerMinute = 60, + dropSize = { x: 0.1, y: 0.1, z: 0.1 }, + dropFallSpeed = 1, // m/s + dropLifetime = 60, // Seconds + dropSpinMax = 0, // Maximum angular velocity per axis; deg/s + debug = false, // Display origin circle; don't use running on Stack Manager + // Other + squallCircle, + SQUALL_CIRCLE_COLOR = { red: 255, green: 0, blue: 0 }, + SQUALL_CIRCLE_ALPHA = 0.5, + SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0), + raindropProperties, + RAINDROP_MODEL_URL = "https://s3.amazonaws.com/hifi-public/ozan/Polyworld/Sets/sky/tetrahedron_v1-Faceted2.fbx", + raindropTimer, + raindrops = [], // HACK: Work around raindrops not always getting velocities + raindropVelocities = [], // HACK: Work around raindrops not always getting velocities + DEGREES_TO_RADIANS = Math.PI / 180; + + function processProperties() { + if (!properties.hasOwnProperty("origin")) { + print("ERROR: Rain squall origin must be specified"); + return; + } + squallOrigin = properties.origin; + + if (!properties.hasOwnProperty("radius")) { + print("ERROR: Rain squall radius must be specified"); + return; + } + squallRadius = properties.radius; + + if (properties.hasOwnProperty("dropsPerMinute")) { + dropsPerMinute = properties.dropsPerMinute; + } + + if (properties.hasOwnProperty("dropSize")) { + dropSize = properties.dropSize; + } + + if (properties.hasOwnProperty("dropFallSpeed")) { + dropFallSpeed = properties.dropFallSpeed; + } + + if (properties.hasOwnProperty("dropLifetime")) { + dropLifetime = properties.dropLifetime; + } + + if (properties.hasOwnProperty("dropSpinMax")) { + dropSpinMax = properties.dropSpinMax; + } + + if (properties.hasOwnProperty("debug")) { + debug = properties.debug; + } + + raindropProperties = { + type: "Model", + modelURL: RAINDROP_MODEL_URL, + lifetime: dropLifetime, + dimensions: dropSize, + velocity: { x: 0, y: -dropFallSpeed, z: 0 }, + damping: 0, + angularDamping: 0, + ignoreForCollisions: true + }; + } + + function createRaindrop() { + var angle, + radius, + offset, + drop, + spin = { x: 0, y: 0, z: 0 }, + maxSpinRadians = properties.dropSpinMax * DEGREES_TO_RADIANS, + i; + + // HACK: Work around rain drops not always getting velocities set at creation + for (i = 0; i < raindrops.length; i += 1) { + Entities.editEntity(raindrops[i], raindropVelocities[i]); + } + + angle = Math.random() * 360; + radius = Math.random() * squallRadius; + offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), { x: 0, y: -0.1, z: radius }); + raindropProperties.position = Vec3.sum(squallOrigin, offset); + if (properties.dropSpinMax > 0) { + spin = { + x: Math.random() * maxSpinRadians, + y: Math.random() * maxSpinRadians, + z: Math.random() * maxSpinRadians + }; + raindropProperties.angularVelocity = spin; + } + drop = Entities.addEntity(raindropProperties); + + // HACK: Work around rain drops not always getting velocities set at creation + raindrops.push(drop); + raindropVelocities.push({ + velocity: raindropProperties.velocity, + angularVelocity: spin + }); + if (raindrops.length > 5) { + raindrops.shift(); + raindropVelocities.shift(); + } + } + + function setUp() { + if (debug) { + squallCircle = Overlays.addOverlay("circle3d", { + size: { x: 2 * squallRadius, y: 2 * squallRadius }, + color: SQUALL_CIRCLE_COLOR, + alpha: SQUALL_CIRCLE_ALPHA, + solid: true, + visible: debug, + position: squallOrigin, + rotation: SQUALL_CIRCLE_ROTATION + }); + } + + raindropTimer = Script.setInterval(createRaindrop, 60000 / dropsPerMinute); + } + + function tearDown() { + Script.clearInterval(raindropTimer); + if (debug) { + Overlays.deleteOverlay(squallCircle); + } + } + + processProperties(); + setUp(); + Script.scriptEnding.connect(tearDown); + + return { + }; +}; + +var rainSquall1 = new RainSquall({ + origin: { x: 1195, y: 1223, z: 1020 }, + radius: 25, + dropsPerMinute: 120, + dropSize: { x: 0.1, y: 0.1, z: 0.1 }, + dropFallSpeed: 3, + dropLifetime: 30, + dropSpinMax: 180, + debug: false +}); diff --git a/examples/avatarSelector.js b/examples/avatarSelector.js new file mode 100644 index 0000000000..dc2916a1a8 --- /dev/null +++ b/examples/avatarSelector.js @@ -0,0 +1,416 @@ +// +// avatarSelector.js +// examples +// +// Created by David Rowe on 21 Apr 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Based on lobby.js created by Stephen Birarda on 17 Oct 2014. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var panelWall = false; +var orbShell = false; +var descriptionText = false; +var showText = false; + +// used for formating the description text, in meters +var textWidth = 4; +var textHeight = .5; +var numberOfLines = 2; +var textMargin = 0.0625; +var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; + +var avatarStickPosition = {}; + +var orbNaturalExtentsMin = { x: -1.230354, y: -1.22077, z: -1.210487 }; +var orbNaturalExtentsMax = { x: 1.230353, y: 1.229819, z: 1.210487 }; +var panelsNaturalExtentsMin = { x: -1.223182, y: -0.348487, z: 0.0451369 }; +var panelsNaturalExtentsMax = { x: 1.223039, y: 0.602978, z: 1.224298 }; + +var orbNaturalDimensions = Vec3.subtract(orbNaturalExtentsMax, orbNaturalExtentsMin); +var panelsNaturalDimensions = Vec3.subtract(panelsNaturalExtentsMax, panelsNaturalExtentsMin); + +var SCALING_FACTOR = 10; +var orbDimensions = Vec3.multiply(orbNaturalDimensions, SCALING_FACTOR); +var panelsDimensions = Vec3.multiply(panelsNaturalDimensions, SCALING_FACTOR); + +var orbNaturalCenter = Vec3.sum(orbNaturalExtentsMin, Vec3.multiply(orbNaturalDimensions, 0.5)); +var panelsNaturalCenter = Vec3.sum(panelsNaturalExtentsMin, Vec3.multiply(panelsNaturalDimensions, 0.5)); +var orbCenter = Vec3.multiply(orbNaturalCenter, SCALING_FACTOR); +var panelsCenter = Vec3.multiply(panelsNaturalCenter, SCALING_FACTOR); +var panelsCenterShift = Vec3.subtract(panelsCenter, orbCenter); + +var ORB_SHIFT = { x: 0, y: -1.4, z: -0.8 }; + +var LOBBY_PANEL_WALL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/PanelWallForInterface.fbx"; +var LOBBY_BLANK_PANEL_TEXTURE_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/Texture.jpg"; +var LOBBY_SHELL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/LobbyShellForInterface.fbx"; + +var droneSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/drone.stereo.raw") +var currentDrone = null; + +var latinSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/latin.stereo.raw") +var latinInjector = null; +var elevatorSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/elevator.stereo.raw") +var elevatorInjector = null; +var currentMuzakInjector = null; +var currentSound = null; + +function textOverlayPosition() { + var TEXT_DISTANCE_OUT = 6; + var TEXT_DISTANCE_DOWN = -2; + return Vec3.sum(Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), TEXT_DISTANCE_OUT)), + Vec3.multiply(Quat.getUp(Camera.orientation), TEXT_DISTANCE_DOWN)); +} + +var panelPlaceOrder = [ + 7, 8, 9, 10, 11, 12, 13, + 0, 1, 2, 3, 4, 5, 6, + 14, 15, 16, 17, 18, 19, 20 +]; + +// Avatar index is 0-based +function avatarIndexToPanelIndex(avatarIndex) { + return panelPlaceOrder.indexOf(avatarIndex) + 1; +} + +// Panel index is 1-based +function panelIndexToPlaceIndex(panelIndex) { + return panelPlaceOrder[panelIndex - 1]; +} + +var MAX_NUM_PANELS = 21; +var DRONE_VOLUME = 0.3; + +function drawLobby() { + if (!panelWall) { + print("Adding overlays for the avatar selector panel wall and orb shell."); + + var cameraEuler = Quat.safeEulerAngles(Camera.orientation); + var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0 }); + + var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT)); + + var panelWallProps = { + url: LOBBY_PANEL_WALL_URL, + position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)), + rotation: towardsMe, + dimensions: panelsDimensions + }; + + var orbShellProps = { + url: LOBBY_SHELL_URL, + position: orbPosition, + rotation: towardsMe, + dimensions: orbDimensions, + ignoreRayIntersection: true + }; + + var windowDimensions = Controller.getViewportDimensions(); + + var descriptionTextProps = { + position: textOverlayPosition(), + dimensions: { x: textWidth, y: textHeight }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + color: { red: 255, green: 255, blue: 255 }, + topMargin: textMargin, + leftMargin: textMargin, + bottomMargin: textMargin, + rightMargin: textMargin, + text: "", + lineHeight: lineHeight, + alpha: 0.9, + backgroundAlpha: 0.9, + ignoreRayIntersection: true, + visible: false, + isFacingAvatar: true + }; + + avatarStickPosition = MyAvatar.position; + + panelWall = Overlays.addOverlay("model", panelWallProps); + orbShell = Overlays.addOverlay("model", orbShellProps); + descriptionText = Overlays.addOverlay("text3d", descriptionTextProps); + + if (droneSound.downloaded) { + // start the drone sound + if (!currentDrone) { + currentDrone = Audio.playSound(droneSound, { stereo: true, loop: true, localOnly: true, volume: DRONE_VOLUME }); + } else { + currentDrone.restart(); + } + } + + // start one of our muzak sounds + playRandomMuzak(); + } +} + +var avatars = {}; + +function changeLobbyTextures() { + var req = new XMLHttpRequest(); + req.open("GET", "https://metaverse.highfidelity.com/api/v1/marketplace?category=head+%26+body&limit=21", false); + req.send(); // Data returned is randomized. + + avatars = JSON.parse(req.responseText).data.items; + + var NUM_PANELS = avatars.length; + + var textureProp = { + textures: {} + }; + + for (var j = 0; j < NUM_PANELS; j++) { + var panelIndex = avatarIndexToPanelIndex(j); + textureProp["textures"]["file" + panelIndex] = avatars[j].preview_url; + }; + + Overlays.editOverlay(panelWall, textureProp); +} + +var MUZAK_VOLUME = 0.1; + +function playCurrentSound(secondOffset) { + if (currentSound == latinSound) { + if (!latinInjector) { + latinInjector = Audio.playSound(latinSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME }); + } else { + latinInjector.restart(); + } + + currentMuzakInjector = latinInjector; + } else if (currentSound == elevatorSound) { + if (!elevatorInjector) { + elevatorInjector = Audio.playSound(elevatorSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME }); + } else { + elevatorInjector.restart(); + } + + currentMuzakInjector = elevatorInjector; + } +} + +function playNextMuzak() { + if (panelWall) { + if (currentSound == latinSound) { + if (elevatorSound.downloaded) { + currentSound = elevatorSound; + } + } else if (currentSound == elevatorSound) { + if (latinSound.downloaded) { + currentSound = latinSound; + } + } + + playCurrentSound(0); + } +} + +function playRandomMuzak() { + currentSound = null; + + if (latinSound.downloaded && elevatorSound.downloaded) { + currentSound = Math.random() < 0.5 ? latinSound : elevatorSound; + } else if (latinSound.downloaded) { + currentSound = latinSound; + } else if (elevatorSound.downloaded) { + currentSound = elevatorSound; + } + + if (currentSound) { + // pick a random number of seconds from 0-10 to offset the muzak + var secondOffset = Math.random() * 10; + + playCurrentSound(secondOffset); + } else { + currentMuzakInjector = null; + } +} + +function cleanupLobby() { + toggleEnvironmentRendering(true); + + // for each of the 21 placeholder textures, set them back to default so the cached model doesn't have changed textures + var panelTexturesReset = {}; + panelTexturesReset["textures"] = {}; + + for (var j = 0; j < MAX_NUM_PANELS; j++) { + panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL; + }; + + Overlays.editOverlay(panelWall, panelTexturesReset); + + Overlays.deleteOverlay(panelWall); + Overlays.deleteOverlay(orbShell); + Overlays.deleteOverlay(descriptionText); + + panelWall = false; + orbShell = false; + + if (currentDrone) { + currentDrone.stop(); + currentDrone = null + } + + if (currentMuzakInjector) { + currentMuzakInjector.stop(); + currentMuzakInjector = null; + } + + avatars = {}; + +} + +function actionStartEvent(event) { + if (panelWall) { + // we've got an action event and our panel wall is up + // check if we hit a panel and if we should jump there + var result = Overlays.findRayIntersection(event.actionRay); + if (result.intersects && result.overlayID == panelWall) { + + var panelName = result.extraInfo; + + var panelStringIndex = panelName.indexOf("Panel"); + if (panelStringIndex != -1) { + var panelIndex = parseInt(panelName.slice(5)); + var avatarIndex = panelIndexToPlaceIndex(panelIndex); + if (avatarIndex < avatars.length) { + var actionPlace = avatars[avatarIndex]; + + print("Changing avatar to " + actionPlace.name + + " after click on panel " + panelIndex + " with avatar index " + avatarIndex); + + MyAvatar.useFullAvatarURL(actionPlace.content_url); + + maybeCleanupLobby(); + } + } + } + } +} + +var control = false; + +function keyPressEvent(event) { + if (event.text === "CONTROL") { + control = true; + } + + if (control && event.text === "a") { + if (!panelWall) { + toggleEnvironmentRendering(false); + drawLobby(); + changeLobbyTextures(); + } else { + cleanupLobby(); + } + } +} + +function keyReleaseEvent(event) { + if (event.text === "CONTROL") { + control = false; + } +} + +var CLEANUP_EPSILON_DISTANCE = 0.05; + +function maybeCleanupLobby() { + if (panelWall && Vec3.length(Vec3.subtract(avatarStickPosition, MyAvatar.position)) > CLEANUP_EPSILON_DISTANCE) { + cleanupLobby(); + } +} + +function toggleEnvironmentRendering(shouldRender) { + Scene.shouldRenderAvatars = shouldRender; + Scene.shouldRenderEntities = shouldRender; +} + +function handleLookAt(pickRay) { + if (panelWall && descriptionText) { + // we've got an action event and our panel wall is up + // check if we hit a panel and if we should jump there + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects && result.overlayID == panelWall) { + var panelName = result.extraInfo; + var panelStringIndex = panelName.indexOf("Panel"); + if (panelStringIndex != -1) { + var panelIndex = parseInt(panelName.slice(5)); + var avatarIndex = panelIndexToPlaceIndex(panelIndex); + if (avatarIndex < avatars.length) { + var actionPlace = avatars[avatarIndex]; + + if (actionPlace.description == "") { + Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText }); + } else { + // handle line wrapping + var allWords = actionPlace.description.split(" "); + var currentGoodLine = ""; + var currentTestLine = ""; + var formatedDescription = ""; + var wordsFormated = 0; + var currentTestWord = 0; + var wordsOnLine = 0; + while (wordsFormated < allWords.length) { + // first add the "next word" to the line and test it. + currentTestLine = currentGoodLine; + if (wordsOnLine > 0) { + currentTestLine += " " + allWords[currentTestWord]; + } else { + currentTestLine = allWords[currentTestWord]; + } + var lineLength = Overlays.textSize(descriptionText, currentTestLine).width; + if (lineLength < textWidth || wordsOnLine == 0) { + wordsFormated++; + currentTestWord++; + wordsOnLine++; + currentGoodLine = currentTestLine; + } else { + formatedDescription += currentGoodLine + "\n"; + wordsOnLine = 0; + currentGoodLine = ""; + currentTestLine = ""; + } + } + formatedDescription += currentGoodLine; + Overlays.editOverlay(descriptionText, { text: formatedDescription, visible: showText }); + } + } else { + Overlays.editOverlay(descriptionText, { text: "", visible: false }); + } + } + } + } +} + +function update(deltaTime) { + maybeCleanupLobby(); + if (panelWall) { + Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); + + // if the reticle is up then we may need to play the next muzak + if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + playNextMuzak(); + } + } +} + +function mouseMoveEvent(event) { + if (panelWall) { + var pickRay = Camera.computePickRay(event.x, event.y); + handleLookAt(pickRay); + } +} + +Controller.actionStartEvent.connect(actionStartEvent); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.update.connect(update); +Script.scriptEnding.connect(maybeCleanupLobby); +Controller.mouseMoveEvent.connect(mouseMoveEvent); diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 05ffb0bd3f..a5c086fc44 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -18,3 +18,4 @@ Script.load("lobby.js"); Script.load("notifications.js"); Script.load("look.js"); Script.load("users.js"); +Script.load("grab.js"); diff --git a/examples/edit.js b/examples/edit.js index 2b32769337..585b45f3f9 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -28,6 +28,7 @@ Script.include([ "libraries/gridTool.js", "libraries/entityList.js", "libraries/lightOverlayManager.js", + "libraries/zoneOverlayManager.js", ]); var selectionDisplay = SelectionDisplay; @@ -35,6 +36,7 @@ var selectionManager = SelectionManager; var entityPropertyDialogBox = EntityPropertyDialogBox; var lightOverlayManager = new LightOverlayManager(); +var zoneOverlayManager = new ZoneOverlayManager(); var cameraManager = new CameraManager(); @@ -47,6 +49,7 @@ var entityListTool = EntityListTool(); selectionManager.addEventListener(function() { selectionDisplay.updateHandles(); lightOverlayManager.updatePositions(); + zoneOverlayManager.updatePositions(); }); var windowDimensions = Controller.getViewportDimensions(); @@ -77,11 +80,13 @@ var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; var MENU_SHOW_LIGHTS_IN_EDIT_MODE = "Show Lights in Edit Mode"; +var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Edit Mode"; var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled"; var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode"; +var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; 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." @@ -135,6 +140,7 @@ var toolBar = (function () { newSphereButton, newLightButton, newTextButton, + newZoneButton, browseMarketplaceButton; function initialize() { @@ -201,6 +207,14 @@ var toolBar = (function () { alpha: 0.9, visible: false }); + newZoneButton = toolBar.addTool({ + imageURL: toolIconUrl + "zonecube_text.svg", + subImage: { x: 0, y: 128, width: 128, height: 128 }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: false + }); that.setActive(false); } @@ -232,6 +246,7 @@ var toolBar = (function () { } toolBar.selectTool(activeButton, isActive); lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); + zoneOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); }; // Sets visibility of tool buttons, excluding the power button @@ -241,6 +256,7 @@ var toolBar = (function () { toolBar.showTool(newSphereButton, doShow); toolBar.showTool(newLightButton, doShow); toolBar.showTool(newTextButton, doShow); + toolBar.showTool(newZoneButton, doShow); }; var RESIZE_INTERVAL = 50; @@ -412,6 +428,21 @@ var toolBar = (function () { return true; } + if (newZoneButton === toolBar.clicked(clickedOverlay)) { + var position = getPositionToCreateEntity(); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + placingEntityID = Entities.addEntity({ + type: "Zone", + position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS), + dimensions: { x: 10, y: 10, z: 10 }, + }); + } else { + print("Can't create box: Text would be out of bounds."); + } + return true; + } + return false; }; @@ -486,12 +517,22 @@ function rayPlaneIntersection(pickRay, point, normal) { } function findClickedEntity(event) { + var pickZones = event.isControl; + + if (pickZones) { + Entities.setZonesArePickable(true); + } + var pickRay = Camera.computePickRay(event.x, event.y); var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking var lightResult = lightOverlayManager.findRayIntersection(pickRay); lightResult.accurate = true; + if (pickZones) { + Entities.setZonesArePickable(false); + } + var result; if (!entityResult.intersects && !lightResult.intersects) { @@ -551,7 +592,11 @@ var idleMouseTimerId = null; var IDLE_MOUSE_TIMEOUT = 200; var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0; -function mouseMoveEvent(event) { +var lastMouseMoveEvent = null; +function mouseMoveEventBuffered(event) { + lastMouseMoveEvent = event; +} +function mouseMove(event) { mouseHasMovedSincePress = true; if (placingEntityID) { @@ -620,6 +665,10 @@ function highlightEntityUnderCursor(position, accurateRay) { function mouseReleaseEvent(event) { + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } if (propertyMenu.mouseReleaseEvent(event) || toolBar.mouseReleaseEvent(event)) { return true; } @@ -731,7 +780,7 @@ function mouseClickEvent(event) { } Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); @@ -776,6 +825,8 @@ function setupModelMenus() { isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" }); Menu.addMenuItem({ menuName: "View", menuItemName: MENU_SHOW_LIGHTS_IN_EDIT_MODE, afterItem: MENU_EASE_ON_FOCUS, isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE) == "true" }); + Menu.addMenuItem({ menuName: "View", menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE, afterItem: MENU_SHOW_LIGHTS_IN_EDIT_MODE, + isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) == "true" }); Entities.setLightsArePickable(false); } @@ -804,12 +855,14 @@ function cleanupModelMenus() { Menu.removeMenuItem("View", MENU_AUTO_FOCUS_ON_SELECT); Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS); Menu.removeMenuItem("View", MENU_SHOW_LIGHTS_IN_EDIT_MODE); + Menu.removeMenuItem("View", MENU_SHOW_ZONES_IN_EDIT_MODE); } Script.scriptEnding.connect(function() { Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); Settings.setValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); + Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); progressDialog.cleanup(); toolBar.cleanup(); @@ -837,6 +890,10 @@ Script.update.connect(function (deltaTime) { lastOrientation = Camera.orientation; lastPosition = Camera.position; } + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } }); function insideBox(center, dimensions, point) { @@ -942,6 +999,8 @@ function handeMenuEvent(menuItem) { selectAllEtitiesInCurrentSelectionBox(true); } else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) { lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); + } else if (menuItem == MENU_SHOW_ZONES_IN_EDIT_MODE) { + zoneOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); } tooltip.show(false); } @@ -1154,6 +1213,9 @@ PropertiesTool = function(opts) { data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z); } Entities.editEntity(selectionManager.selections[0], data.properties); + if (data.properties.name != undefined) { + entityListTool.sendUpdate(); + } } pushCommandForSelections(); selectionManager._update(); diff --git a/examples/example/entities/fullDomainZoneEntityExample.js b/examples/example/entities/fullDomainZoneEntityExample.js new file mode 100644 index 0000000000..e422cfd398 --- /dev/null +++ b/examples/example/entities/fullDomainZoneEntityExample.js @@ -0,0 +1,23 @@ +// +// fullDomainZoneEntityExample.js +// examples +// +// Created by Brad Hefta-Gaub on 4/16/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is an example script that demonstrates creating a Zone which is as large as the entire domain +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Entities.addEntity({ + type: "Zone", + position: { x: -10000, y: -10000, z: -10000 }, + dimensions: { x: 30000, y: 30000, z: 30000 }, + keyLightColor: { red: 255, green: 255, blue: 0 }, + keyLightDirection: { x: 0, y: -1.0, z: 0 }, + keyLightIntensity: 1.0, + keyLightAmbientIntensity: 0.5 +}); + diff --git a/examples/example/entities/zoneEntityExample.js b/examples/example/entities/zoneEntityExample.js new file mode 100644 index 0000000000..b5831a2bb5 --- /dev/null +++ b/examples/example/entities/zoneEntityExample.js @@ -0,0 +1,72 @@ +// +// zoneEntityExample.js +// examples +// +// Created by Brad Hefta-Gaub on 4/16/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is an example script that demonstrates creating and editing a entity +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var count = 0; +var stopAfter = 1000; + +var zoneEntityA = Entities.addEntity({ + type: "Zone", + position: { x: 5, y: 5, z: 5 }, + dimensions: { x: 10, y: 10, z: 10 }, + keyLightColor: { red: 255, green: 0, blue: 0 }, + stageSunModelEnabled: false, + keyLightDirection: { x: 0, y: -1.0, z: 0 }, + shapeType: "sphere" +}); + +print("zoneEntityA:" + zoneEntityA); + +var zoneEntityB = Entities.addEntity({ + type: "Zone", + position: { x: 5, y: 5, z: 5 }, + dimensions: { x: 2, y: 2, z: 2 }, + keyLightColor: { red: 0, green: 255, blue: 0 }, + keyLightIntensity: 0.9, + stageLatitude: 37.777, + stageLongitude: 122.407, + stageAltitude: 0.03, + stageDay: 60, + stageHour: 12, + stageSunModelEnabled: true +}); + +print("zoneEntityB:" + zoneEntityB); + + +var zoneEntityC = Entities.addEntity({ + type: "Zone", + position: { x: 5, y: 10, z: 5 }, + dimensions: { x: 10, y: 10, z: 10 }, + keyLightColor: { red: 0, green: 0, blue: 255 }, + keyLightIntensity: 0.75, + keyLightDirection: { x: 0, y: 0, z: -1 }, + stageSunModelEnabled: false, + shapeType: "compound", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/cube.fbx" +}); + +print("zoneEntityC:" + zoneEntityC); + + +// register the call back so it fires before each data send +Script.update.connect(function(deltaTime) { + // stop it... + if (count >= stopAfter) { + print("calling Script.stop()"); + Script.stop(); + } + count++; +}); + diff --git a/examples/example/games/billiards.js b/examples/example/games/billiards.js index d4bc71ba37..8e890515c0 100644 --- a/examples/example/games/billiards.js +++ b/examples/example/games/billiards.js @@ -39,126 +39,130 @@ hitSounds.push(SoundCache.getSound(HIFI_PUBLIC_BUCKET + "Collisions-ballhitsandc HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); var reticle = Overlays.addOverlay("image", { - x: screenSize.x / 2 - 16, - y: screenSize.y / 2 - 16, - width: 32, - height: 32, - imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png", - color: { red: 255, green: 255, blue: 255}, - alpha: 1 - }); + x: screenSize.x / 2 - 16, + y: screenSize.y / 2 - 16, + width: 32, + height: 32, + imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 +}); function makeTable(pos) { - // Top - tableParts.push(Entities.addEntity( + // Top + tableParts.push(Entities.addEntity( { type: "Box", - position: pos, - dimensions: { x: LENGTH * SCALE, y: HEIGHT, z: WIDTH * SCALE }, - color: { red: 0, green: 255, blue: 0 } })); - // Long Bumpers - tableParts.push(Entities.addEntity( + position: pos, + dimensions: { x: LENGTH * SCALE, y: HEIGHT, z: WIDTH * SCALE }, + color: { red: 0, green: 255, blue: 0 } })); + // Long Bumpers + tableParts.push(Entities.addEntity( { type: "Box", - position: { x: pos.x - LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); - tableParts.push(Entities.addEntity( + position: { x: pos.x - LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + tableParts.push(Entities.addEntity( { type: "Box", - position: { x: pos.x + LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + position: { x: pos.x + LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); - tableParts.push(Entities.addEntity( + tableParts.push(Entities.addEntity( { type: "Box", - position: { x: pos.x - LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); - tableParts.push(Entities.addEntity( + position: { x: pos.x - LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + tableParts.push(Entities.addEntity( { type: "Box", - position: { x: pos.x + LENGTH / 2.0, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, - dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); - // End bumpers - tableParts.push(Entities.addEntity( + position: { x: pos.x + LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + // End bumpers + tableParts.push(Entities.addEntity( { type: "Box", - position: { x: pos.x + (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z }, - dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + position: { x: pos.x + (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z }, + dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); - tableParts.push(Entities.addEntity( + tableParts.push(Entities.addEntity( { type: "Box", - position: { x: pos.x - (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, - y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), - z: pos.z }, - dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, - color: { red: 237, green: 201, blue: 175 } })); + position: { x: pos.x - (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z }, + dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); } function makeBalls(pos) { - // Object balls + // Object balls var whichBall = [ 1, 14, 15, 4, 8, 7, 12, 9, 3, 13, 10, 5, 6, 11, 2 ]; var ballNumber = 0; - var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; - for (var row = 1; row <= 5; row++) { - ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE); - for (var spot = 0; spot < row; spot++) { - balls.push(Entities.addEntity( + var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; + for (var row = 1; row <= 5; row++) { + ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE); + for (var spot = 0; spot < row; spot++) { + balls.push(Entities.addEntity( { type: "Model", - modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/ball_" + whichBall[ballNumber].toString() + ".fbx", - position: ballPosition, - dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, - rotation: Quat.fromPitchYawRollDegrees((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20), - color: { red: 255, green: 255, blue: 255 }, - gravity: { x: 0, y: GRAVITY, z: 0 }, - ignoreCollisions: false, - damping: 0.50, - shapeType: "sphere", - collisionsWillMove: true })); - ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; + modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/ball_" + + whichBall[ballNumber].toString() + ".fbx", + position: ballPosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + rotation: Quat.fromPitchYawRollDegrees((Math.random() - 0.5) * 20, + (Math.random() - 0.5) * 20, + (Math.random() - 0.5) * 20), + color: { red: 255, green: 255, blue: 255 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: {x: 0, y: -0.2, z: 0 }, + ignoreCollisions: false, + damping: 0.50, + shapeType: "sphere", + collisionsWillMove: true })); + ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; ballNumber++; - } - ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE; } + ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE; + } // Cue Ball cuePosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; cueBall = Entities.addEntity( - { type: "Model", - modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/cue_ball.fbx", - position: cuePosition, - dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, - color: { red: 255, green: 255, blue: 255 }, - gravity: { x: 0, y: GRAVITY, z: 0 }, - angularVelocity: { x: 0, y: 0, z: 0 }, - velocity: {x: 0, y: 0, z: 0 }, - ignoreCollisions: false, - damping: 0.50, - shapeType: "sphere", - collisionsWillMove: true }); - + { type: "Model", + modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/cue_ball.fbx", + position: cuePosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + color: { red: 255, green: 255, blue: 255 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + angularVelocity: { x: 0, y: 0, z: 0 }, + velocity: {x: 0, y: -0.2, z: 0 }, + ignoreCollisions: false, + damping: 0.50, + shapeType: "sphere", + collisionsWillMove: true }); + } function isObjectBall(id) { - for (var i; i < balls.length; i++) { - if (balls[i].id == id) { - return true; - } - } - return false; + for (var i; i < balls.length; i++) { + if (balls[i].id == id) { + return true; + } + } + return false; } function shootCue(velocity) { - var DISTANCE_FROM_CAMERA = BALL_SIZE * 5.0 * SCALE; + var DISTANCE_FROM_CAMERA = BALL_SIZE * 5.0 * SCALE; var camera = Camera.getPosition(); var forwardVector = Quat.getFront(Camera.getOrientation()); var cuePosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); @@ -180,14 +184,14 @@ function shootCue(velocity) { density: 8000, ignoreCollisions: false, collisionsWillMove: true - }); + }); print("Shot, velocity = " + velocity); } function keyReleaseEvent(event) { - if ((startStroke > 0) && event.text == "SPACE") { - var endTime = new Date().getTime(); - var delta = endTime - startStroke; + if ((startStroke > 0) && event.text == "SPACE") { + var endTime = new Date().getTime(); + var delta = endTime - startStroke; shootCue(delta / 100.0); startStroke = 0; } @@ -201,49 +205,49 @@ function keyPressEvent(event) { } function cleanup() { - for (var i = 0; i < tableParts.length; i++) { - if (!tableParts[i].isKnownID) { - tableParts[i] = Entities.identifyEntity(tableParts[i]); - } - Entities.deleteEntity(tableParts[i]); + for (var i = 0; i < tableParts.length; i++) { + if (!tableParts[i].isKnownID) { + tableParts[i] = Entities.identifyEntity(tableParts[i]); } - for (var i = 0; i < balls.length; i++) { - if (!balls[i].isKnownID) { - balls[i] = Entities.identifyEntity(balls[i]); - } - Entities.deleteEntity(balls[i]); + Entities.deleteEntity(tableParts[i]); + } + for (var i = 0; i < balls.length; i++) { + if (!balls[i].isKnownID) { + balls[i] = Entities.identifyEntity(balls[i]); } - Overlays.deleteOverlay(reticle); - Entities.deleteEntity(cueBall); + Entities.deleteEntity(balls[i]); + } + Overlays.deleteOverlay(reticle); + Entities.deleteEntity(cueBall); } function update(deltaTime) { - if (!cueBall.isKnownID) { - cueBall = Entities.identifyEntity(cueBall); - } else { - // Check if cue ball has fallen off table, re-drop if so - var cueProperties = Entities.getEntityProperties(cueBall); - if (cueProperties.position.y < tableCenter.y) { - // Replace the cueball - Entities.editEntity(cueBall, { position: cuePosition } ); + if (!cueBall.isKnownID) { + cueBall = Entities.identifyEntity(cueBall); + } else { + // Check if cue ball has fallen off table, re-drop if so + var cueProperties = Entities.getEntityProperties(cueBall); + if (cueProperties.position.y < tableCenter.y) { + // Replace the cueball + Entities.editEntity(cueBall, { position: cuePosition } ); - } } + } } function entityCollisionWithEntity(entity1, entity2, collision) { - /* - NOT WORKING YET - if ((entity1.id == cueBall.id) || (entity2.id == cueBall.id)) { - print("Cue ball collision!"); - //audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - //Audio.playSound(hitSounds[0], { position: Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())) }); - } + /* + NOT WORKING YET + if ((entity1.id == cueBall.id) || (entity2.id == cueBall.id)) { + print("Cue ball collision!"); + //audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + //Audio.playSound(hitSounds[0], { position: Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())) }); + } - else if (isObjectBall(entity1.id) || isObjectBall(entity2.id)) { - print("Object ball collision"); - } */ + else if (isObjectBall(entity1.id) || isObjectBall(entity2.id)) { + print("Object ball collision"); + } */ } tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); diff --git a/examples/example/misc/listAllScripts.js b/examples/example/misc/listAllScripts.js new file mode 100644 index 0000000000..1baa40751e --- /dev/null +++ b/examples/example/misc/listAllScripts.js @@ -0,0 +1,41 @@ +// +// listAllScripts.js +// examples/example/misc +// +// Created by Thijs Wenker on 7 Apr 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Outputs the running, public and local scripts to the console. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var runningScripts = ScriptDiscoveryService.getRunning(); +print("Running Scripts:"); +for (var i = 0; i < runningScripts.length; i++) { + print(" - " + runningScripts[i].name + (runningScripts[i].local ? "[Local]" : "") + " {" + runningScripts[i].url + "}"); +} + +var localScripts = ScriptDiscoveryService.getLocal(); +print("Local Scripts:"); +for (var i = 0; i < localScripts.length; i++) { + print(" - " + localScripts[i].name + " {" + localScripts[i].path + "}"); +} + +// recursive function to walk through all folders in public scripts +// adding 2 spaces to the prefix per depth level +function displayPublicScriptFolder(nodes, prefix) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].type == "folder") { + print(prefix + "<" + nodes[i].name + ">"); + displayPublicScriptFolder(nodes[i].children, " " + prefix); + continue; + } + print(prefix + nodes[i].name + " {" + nodes[i].url + "}"); + } +} + +var publicScripts = ScriptDiscoveryService.getPublic(); +print("Public Scripts:"); +displayPublicScriptFolder(publicScripts, " - "); \ No newline at end of file diff --git a/examples/example/misc/sunLightExample.js b/examples/example/misc/sunLightExample.js index e6c06bb1ae..45cf353620 100644 --- a/examples/example/misc/sunLightExample.js +++ b/examples/example/misc/sunLightExample.js @@ -41,7 +41,8 @@ panel.newSlider("Year Time", 0, 364, ); panel.newSlider("Day Time", 0, 24, - function(value) { Scene.setStageDayTime(value); }, + + function(value) { Scene.setStageDayTime(value); panel.update("Light Direction"); }, function() { return Scene.getStageDayTime(); }, function(value) { var hour = Math.floor(value); @@ -72,12 +73,36 @@ function runStageTime() { } Script.setInterval(runStageTime, tickTackPeriod); +panel.newCheckbox("Enable Sun Model", + function(value) { Scene.setStageSunModelEnable((value != 0)); }, + function() { return Scene.isStageSunModelEnabled(); }, + function(value) { return (value); } +); + +panel.newDirectionBox("Light Direction", + function(value) { Scene.setKeyLightDirection(value); }, + function() { return Scene.getKeyLightDirection(); }, + function(value) { return value.x.toFixed(2) + "," + value.y.toFixed(2) + "," + value.z.toFixed(2); } +); + panel.newSlider("Light Intensity", 0.0, 5, - function(value) { Scene.setSunIntensity(value); }, - function() { return Scene.getSunIntensity(); }, + function(value) { Scene.setKeyLightIntensity(value); }, + function() { return Scene.getKeyLightIntensity(); }, function(value) { return (value).toFixed(2); } ); +panel.newSlider("Ambient Light Intensity", 0.0, 1.0, + function(value) { Scene.setKeyLightAmbientIntensity(value); }, + function() { return Scene.getKeyLightAmbientIntensity(); }, + function(value) { return (value).toFixed(2); } +); + +panel.newColorBox("Light Color", + function(value) { Scene.setKeyLightColor(value); }, + function() { return Scene.getKeyLightColor(); }, + function(value) { return (value); } // "(" + value.x + "," = value.y + "," + value.z + ")"; } +); + Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); }); Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); }); Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); }); diff --git a/examples/grab.js b/examples/grab.js new file mode 100644 index 0000000000..cd7d59701e --- /dev/null +++ b/examples/grab.js @@ -0,0 +1,166 @@ +var isGrabbing = false; +var grabbedEntity = null; +var prevMouse = {}; +var deltaMouse = { + z: 0 +} +var entityProps; +var targetPosition; +var moveUpDown = false; + +var currentPosition, currentVelocity; + +var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); +var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); + +var DROP_DISTANCE = 5.0; +var DROP_COLOR = { + red: 200, + green: 200, + blue: 200 +}; +var DROP_WIDTH = 2; + + +var dropLine = Overlays.addOverlay("line3d", { + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: DROP_COLOR, + alpha: 1, + visible: false, + lineWidth: DROP_WIDTH +}); + + +function mousePressEvent(event) { + if (!event.isLeftButton) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay); + if (intersection.intersects && intersection.properties.collisionsWillMove) { + grabbedEntity = intersection.entityID; + var props = Entities.getEntityProperties(grabbedEntity) + isGrabbing = true; + targetPosition = props.position; + currentPosition = props.position; + currentVelocity = props.velocity; + updateDropLine(targetPosition); + Audio.playSound(grabSound, { + position: props.position, + volume: 0.4 + }); + } +} + +function updateDropLine(position) { + Overlays.editOverlay(dropLine, { + visible: true, + start: position, + end: Vec3.sum(position, { + x: 0, + y: -DROP_DISTANCE, + z: 0 + }) + }) +} + + +function mouseReleaseEvent() { + if (isGrabbing) { + isGrabbing = false; + Overlays.editOverlay(dropLine, { + visible: false + }); + targetPosition = null; + Audio.playSound(grabSound, { + position: entityProps.position, + volume: 0.25 + }); + + } +} + +function mouseMoveEvent(event) { + if (isGrabbing) { + deltaMouse.x = event.x - prevMouse.x; + if (!moveUpDown) { + deltaMouse.z = event.y - prevMouse.y; + deltaMouse.y = 0; + } else { + deltaMouse.y = (event.y - prevMouse.y) * -1; + deltaMouse.z = 0; + } + // Update the target position by the amount the mouse moved + var camYaw = Quat.safeEulerAngles(Camera.getOrientation()).y; + var dPosition = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), deltaMouse); + // Adjust target position for the object by the mouse move + var avatarEntityDistance = Vec3.distance(Camera.getPosition(), currentPosition); + // Scale distance we want to move by the distance from the camera to the grabbed object + // TODO: Correct SCREEN_TO_METERS to be correct for the actual FOV, resolution + var SCREEN_TO_METERS = 0.001; + targetPosition = Vec3.sum(targetPosition, Vec3.multiply(dPosition, avatarEntityDistance * SCREEN_TO_METERS)); + } + prevMouse.x = event.x; + prevMouse.y = event.y; + +} + + +function keyReleaseEvent(event) { + if (event.text === "SHIFT") { + moveUpDown = false; + } +} + +function keyPressEvent(event) { + if (event.text === "SHIFT") { + moveUpDown = true; + } +} + +function update(deltaTime) { + if (isGrabbing) { + + entityProps = Entities.getEntityProperties(grabbedEntity); + currentPosition = entityProps.position; + currentVelocity = entityProps.velocity; + + var dPosition = Vec3.subtract(targetPosition, currentPosition); + var CLOSE_ENOUGH = 0.001; + if (Vec3.length(dPosition) > CLOSE_ENOUGH) { + // compute current velocity in the direction we want to move + var velocityTowardTarget = Vec3.dot(currentVelocity, Vec3.normalize(dPosition)); + // compute the speed we would like to be going toward the target position + var SPRING_RATE = 0.35; + var DAMPING_RATE = 0.55; + var desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); + // compute how much we want to add to the existing velocity + var addedVelocity = Vec3.subtract(desiredVelocity, velocityTowardTarget); + var newVelocity = Vec3.sum(currentVelocity, addedVelocity); + // Add Damping + newVelocity = Vec3.subtract(newVelocity, Vec3.multiply(newVelocity, DAMPING_RATE)); + // Update entity + Entities.editEntity(grabbedEntity, { + velocity: newVelocity + }) + } + updateDropLine(currentPosition); + } +} + +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.update.connect(update); + diff --git a/examples/html/entityList.html b/examples/html/entityList.html index bcc1c117ea..5795cc2acb 100644 --- a/examples/html/entityList.html +++ b/examples/html/entityList.html @@ -13,7 +13,7 @@ var DESC_STRING = ' ▴'; function loaded() { - entityList = new List('entity-list', { valueNames: ['type', 'url']}); + entityList = new List('entity-list', { valueNames: ['name', 'type', 'url']}); entityList.clear(); elEntityTable = document.getElementById("entity-table"); elEntityTableBody = document.getElementById("entity-table-body"); @@ -22,6 +22,9 @@ elTeleport = document.getElementById("teleport"); elNoEntitiesMessage = document.getElementById("no-entities"); + document.getElementById("entity-name").onclick = function() { + setSortColumn('name'); + }; document.getElementById("entity-type").onclick = function() { setSortColumn('type'); }; @@ -56,31 +59,34 @@ })); } - function addEntity(id, type, url) { + function addEntity(id, name, type, url) { if (entities[id] === undefined) { var urlParts = url.split('/'); var filename = urlParts[urlParts.length - 1]; - entityList.add([{ id: id, type: type, url: filename }], function(items) { + entityList.add([{ id: id, name: name, type: type, url: filename }], function(items) { var el = items[0].elm; var id = items[0]._values.id; entities[id] = { id: id, - name: id, + name: name, el: el, + item: items[0], }; el.setAttribute('id', 'entity_' + id); el.setAttribute('title', url); el.dataset.entityId = id; el.onclick = onRowClicked; el.ondblclick = onRowDoubleClicked; - el.innerHTML }); if (refreshEntityListTimer) { clearTimeout(refreshEntityListTimer); } refreshEntityListTimer = setTimeout(refreshEntityListObject, 50); + } else { + var item = entities[id].item; + item.values({ name: name, url: url }); } } @@ -90,6 +96,7 @@ } var elSortOrder = { + name: document.querySelector('#entity-name .sort-order'), type: document.querySelector('#entity-type .sort-order'), url: document.querySelector('#entity-url .sort-order'), } @@ -164,7 +171,7 @@ elNoEntitiesMessage.style.display = "none"; for (var i = 0; i < newEntities.length; i++) { var id = newEntities[i].id; - addEntity(id, newEntities[i].type, newEntities[i].url); + addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url); } updateSelectedEntities(data.selectedIDs); } @@ -190,6 +197,7 @@ Type  ▾ + Name  ▾ URL @@ -197,6 +205,7 @@ Type Type + Name
URL
diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 4ac1b70e33..3e775ec698 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -95,8 +95,10 @@ }; function loaded() { + var allSections = []; var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); + var elName = document.getElementById("property-name"); var elLocked = document.getElementById("property-locked"); var elVisible = document.getElementById("property-visible"); var elPositionX = document.getElementById("property-pos-x"); @@ -145,12 +147,13 @@ var elScriptURL = document.getElementById("property-script-url"); var elUserData = document.getElementById("property-user-data"); - var elBoxSections = document.querySelectorAll(".box-section"); - var elBoxColorRed = document.getElementById("property-box-red"); - var elBoxColorGreen = document.getElementById("property-box-green"); - var elBoxColorBlue = document.getElementById("property-box-blue"); + var elColorSection = document.getElementById("color-section"); + var elColorRed = document.getElementById("property-color-red"); + var elColorGreen = document.getElementById("property-color-green"); + var elColorBlue = document.getElementById("property-color-blue"); var elLightSections = document.querySelectorAll(".light-section"); + allSections.push(elLightSections); var elLightSpotLight = document.getElementById("property-light-spot-light"); var elLightColorRed = document.getElementById("property-light-color-red"); var elLightColorGreen = document.getElementById("property-light-color-green"); @@ -161,8 +164,10 @@ var elLightCutoff = document.getElementById("property-light-cutoff"); var elModelSections = document.querySelectorAll(".model-section"); + allSections.push(elModelSections); var elModelURL = document.getElementById("property-model-url"); - var elCollisionModelURL = document.getElementById("property-collision-model-url"); + var elShapeType = document.getElementById("property-shape-type"); + var elCompoundShapeURL = document.getElementById("property-compound-shape-url"); var elModelAnimationURL = document.getElementById("property-model-animation-url"); var elModelAnimationPlaying = document.getElementById("property-model-animation-playing"); var elModelAnimationFPS = document.getElementById("property-model-animation-fps"); @@ -170,9 +175,9 @@ var elModelAnimationSettings = document.getElementById("property-model-animation-settings"); var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); - var elModelShapeType = document.getElementById("property-model-shape"); var elTextSections = document.querySelectorAll(".text-section"); + allSections.push(elTextSections); var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColorRed = document.getElementById("property-text-text-color-red"); @@ -182,6 +187,24 @@ var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); + var elZoneSections = document.querySelectorAll(".zone-section"); + allSections.push(elZoneSections); + var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled"); + var elZoneKeyLightColorRed = document.getElementById("property-zone-key-light-color-red"); + var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); + var elZoneKeyLightColorBlue = document.getElementById("property-zone-key-light-color-blue"); + var elZoneKeyLightIntensity = document.getElementById("property-zone-key-intensity"); + var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity"); + var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); + var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); + var elZoneKeyLightDirectionZ = document.getElementById("property-zone-key-light-direction-z"); + + var elZoneStageLatitude = document.getElementById("property-zone-stage-latitude"); + var elZoneStageLongitude = document.getElementById("property-zone-stage-longitude"); + var elZoneStageAltitude = document.getElementById("property-zone-stage-altitude"); + var elZoneStageDay = document.getElementById("property-zone-stage-day"); + var elZoneStageHour = document.getElementById("property-zone-stage-hour"); + if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); @@ -239,6 +262,8 @@ enableChildren(document.getElementById("properties-list"), 'input'); } + elName.value = properties.name; + elVisible.checked = properties.visible; elPositionX.value = properties.position.x.toFixed(2); @@ -282,31 +307,29 @@ elScriptURL.value = properties.script; elUserData.value = properties.userData; - if (properties.type != "Box") { - for (var i = 0; i < elBoxSections.length; i++) { - elBoxSections[i].style.display = 'none'; + for (var i = 0; i < allSections.length; i++) { + for (var j = 0; j < allSections[i].length; j++) { + allSections[i][j].style.display = 'none'; } - } else { - for (var i = 0; i < elBoxSections.length; i++) { - elBoxSections[i].style.display = 'block'; - } - - elBoxColorRed.value = properties.color.red; - elBoxColorGreen.value = properties.color.green; - elBoxColorBlue.value = properties.color.blue; } - if (properties.type != "Model") { - for (var i = 0; i < elModelSections.length; i++) { - elModelSections[i].style.display = 'none'; - } + if (properties.type == "Box" || properties.type == "Sphere") { + elColorSection.style.display = 'block'; + elColorRed.value = properties.color.red; + elColorGreen.value = properties.color.green; + elColorBlue.value = properties.color.blue; } else { + elColorSection.style.display = 'none'; + } + + if (properties.type == "Model") { for (var i = 0; i < elModelSections.length; i++) { elModelSections[i].style.display = 'block'; } elModelURL.value = properties.modelURL; - elCollisionModelURL.value = properties.collisionModelURL; + elShapeType.value = properties.shapeType; + elCompoundShapeURL.value = properties.compoundShapeURL; elModelAnimationURL.value = properties.animationURL; elModelAnimationPlaying.checked = properties.animationIsPlaying; elModelAnimationFPS.value = properties.animationFPS; @@ -314,14 +337,7 @@ elModelAnimationSettings.value = properties.animationSettings; elModelTextures.value = properties.textures; elModelOriginalTextures.value = properties.originalTextures; - elModelShapeType.value = properties.shapeType; - } - - if (properties.type != "Text") { - for (var i = 0; i < elTextSections.length; i++) { - elTextSections[i].style.display = 'none'; - } - } else { + } else if (properties.type == "Text") { for (var i = 0; i < elTextSections.length; i++) { elTextSections[i].style.display = 'block'; } @@ -334,13 +350,7 @@ elTextBackgroundColorRed.value = properties.backgroundColor.red; elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; - } - - if (properties.type != "Light") { - for (var i = 0; i < elLightSections.length; i++) { - elLightSections[i].style.display = 'none'; - } - } else { + } else if (properties.type == "Light") { for (var i = 0; i < elLightSections.length; i++) { elLightSections[i].style.display = 'block'; } @@ -354,6 +364,28 @@ elLightIntensity.value = properties.intensity; elLightExponent.value = properties.exponent; elLightCutoff.value = properties.cutoff; + } else if (properties.type == "Zone") { + for (var i = 0; i < elZoneSections.length; i++) { + elZoneSections[i].style.display = 'block'; + } + + elZoneStageSunModelEnabled.checked = properties.stageSunModelEnabled; + elZoneKeyLightColorRed.value = properties.keyLightColor.red; + elZoneKeyLightColorGreen.value = properties.keyLightColor.green; + elZoneKeyLightColorBlue.value = properties.keyLightColor.blue; + elZoneKeyLightIntensity.value = properties.keyLightIntensity.toFixed(2); + elZoneKeyLightAmbientIntensity.value = properties.keyLightAmbientIntensity.toFixed(2); + elZoneKeyLightDirectionX.value = properties.keyLightDirection.x.toFixed(2); + elZoneKeyLightDirectionY.value = properties.keyLightDirection.y.toFixed(2); + elZoneKeyLightDirectionZ.value = properties.keyLightDirection.z.toFixed(2); + + elZoneStageLatitude.value = properties.stageLatitude.toFixed(2); + elZoneStageLongitude.value = properties.stageLongitude.toFixed(2); + elZoneStageAltitude.value = properties.stageAltitude.toFixed(2); + elZoneStageDay.value = properties.stageDay; + elZoneStageHour.value = properties.stageHour; + elShapeType.value = properties.shapeType; + elCompoundShapeURL.value = properties.compoundShapeURL; } if (selected) { @@ -366,6 +398,7 @@ } elLocked.addEventListener('change', createEmitCheckedPropertyUpdateFunction('locked')); + elName.addEventListener('change', createEmitTextPropertyUpdateFunction('name')); elVisible.addEventListener('change', createEmitCheckedPropertyUpdateFunction('visible')); var positionChangeFunction = createEmitVec3PropertyUpdateFunction( @@ -425,11 +458,11 @@ elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script')); elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData')); - var boxColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue); - elBoxColorRed.addEventListener('change', boxColorChangeFunction); - elBoxColorGreen.addEventListener('change', boxColorChangeFunction); - elBoxColorBlue.addEventListener('change', boxColorChangeFunction); + var colorChangeFunction = createEmitColorPropertyUpdateFunction( + 'color', elColorRed, elColorGreen, elColorBlue); + elColorRed.addEventListener('change', colorChangeFunction); + elColorGreen.addEventListener('change', colorChangeFunction); + elColorBlue.addEventListener('change', colorChangeFunction); elLightSpotLight.addEventListener('change', createEmitCheckedPropertyUpdateFunction('isSpotlight')); @@ -444,14 +477,14 @@ elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff')); elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL')); - elCollisionModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('collisionModelURL')); + elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType')); + elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL')); elModelAnimationURL.addEventListener('change', createEmitTextPropertyUpdateFunction('animationURL')); elModelAnimationPlaying.addEventListener('change', createEmitCheckedPropertyUpdateFunction('animationIsPlaying')); elModelAnimationFPS.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFPS')); elModelAnimationFrame.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFrameIndex')); elModelAnimationSettings.addEventListener('change', createEmitTextPropertyUpdateFunction('animationSettings')); elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); - elModelShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType')); elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); @@ -468,6 +501,26 @@ elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); + elZoneStageSunModelEnabled.addEventListener('change', createEmitCheckedPropertyUpdateFunction('stageSunModelEnabled')); + var zoneKeyLightColorChangeFunction = createEmitColorPropertyUpdateFunction( + 'keyLightColor', elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); + elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); + elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); + elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); + elZoneKeyLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('keyLightIntensity')); + elZoneKeyLightAmbientIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('keyLightAmbientIntensity')); + var zoneKeyLightDirectionChangeFunction = createEmitVec3PropertyUpdateFunction( + 'keyLightDirection', elZoneKeyLightDirectionX, elZoneKeyLightDirectionY, elZoneKeyLightDirectionZ); + elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); + elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); + elZoneKeyLightDirectionZ.addEventListener('change', zoneKeyLightDirectionChangeFunction); + + elZoneStageLatitude.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageLatitude')); + elZoneStageLongitude.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageLongitude')); + elZoneStageAltitude.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageAltitude')); + elZoneStageDay.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageDay')); + elZoneStageHour.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageHour')); + elMoveSelectionToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", @@ -508,18 +561,22 @@ // To make this work we block the first mouseup event after the elements // received focus. If we block all mouseup events the user will not // be able to click within the selected text. + // We also check to see if the value has changed to make sure we aren't + // blocking a mouse-up event when clicking on an input spinner. var els = document.querySelectorAll("input, textarea"); for (var i = 0; i < els.length; i++) { var clicked = false; + var originalText; els[i].onfocus = function() { + originalText = this.value; this.select(); clicked = false; }; els[i].onmouseup = function(e) { - if (!clicked) { + if (!clicked && originalText == this.value) { e.preventDefault(); - clicked = true; } + clicked = true; }; } } @@ -537,6 +594,12 @@ +
+ Name +
+ +
+
Locked @@ -690,12 +753,12 @@
-
+
Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -707,10 +770,21 @@
-
-
Collision Model URL
+
+
Shape Type
- + +
+
+
+
Compound Shape URL
+
+
@@ -756,18 +830,6 @@
-
-
Shape Type
-
- -
-
- -
Text
@@ -829,6 +891,73 @@
+ +
+ Stage Sun Model Enabled + + + +
+ +
+
Key Light Color
+
+
R
+
G
+
B
+
+
+
+
Key Light Intensity
+
+ +
+
+
+
Key Light Ambient Intensity
+
+ +
+
+
+
Key Light Direction
+
+
Pitch
+
Yaw
+
Roll
+
+
+ +
+
Stage Latitude
+
+ +
+
+
+
Stage Longitude
+
+ +
+
+
+
Stage Altitude
+
+ +
+
+
+
Stage Day
+
+ +
+
+
+
Stage Hour
+
+ +
+
diff --git a/examples/html/style.css b/examples/html/style.css index 6a258d8f02..8be9b92a51 100644 --- a/examples/html/style.css +++ b/examples/html/style.css @@ -301,3 +301,7 @@ input[type="number"]::-webkit-inner-spin-button:hover, input[type="number"]::-webkit-inner-spin-button:active{ opacity: .8; } + +input#property-name { + width: 100%; +} diff --git a/examples/libraries/ToolTip.js b/examples/libraries/ToolTip.js index 680f617436..f6f14d7f2b 100644 --- a/examples/libraries/ToolTip.js +++ b/examples/libraries/ToolTip.js @@ -53,7 +53,8 @@ function Tooltip() { text += "ID: " + properties.id + "\n" if (properties.type == "Model") { text += "Model URL: " + properties.modelURL + "\n" - text += "Collision Model URL: " + properties.collisionModelURL + "\n" + text += "Shape Type: " + properties.shapeType + "\n" + text += "Compound Shape URL: " + properties.compoundShapeURL + "\n" text += "Animation URL: " + properties.animationURL + "\n" text += "Animation is playing: " + properties.animationIsPlaying + "\n" if (properties.sittingPoints && properties.sittingPoints.length > 0) { diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index d0b8ddac7f..e10aed4051 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -31,7 +31,7 @@ EntityListTool = function(opts) { webView.eventBridge.emitScriptEvent(JSON.stringify(data)); }); - function sendUpdate() { + that.sendUpdate = function() { var entities = []; var ids = Entities.findEntities(MyAvatar.position, 100); for (var i = 0; i < ids.length; i++) { @@ -39,6 +39,7 @@ EntityListTool = function(opts) { var properties = Entities.getEntityProperties(id); entities.push({ id: id.id, + name: properties.name, type: properties.type, url: properties.type == "Model" ? properties.modelURL : "", }); @@ -76,7 +77,7 @@ EntityListTool = function(opts) { Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } } else if (data.type == "refresh") { - sendUpdate(); + that.sendUpdate(); } else if (data.type == "teleport") { if (selectionManager.hasSelection()) { MyAvatar.position = selectionManager.worldPosition; diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index d8a91da8f9..5c6c688f0d 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -52,7 +52,9 @@ EntityPropertyDialogBox = (function () { if (properties.type == "Model") { array.push({ label: "Model URL:", value: properties.modelURL }); index++; - array.push({ label: "Collision Model URL:", value: properties.collisionModelURL }); + array.push({ label: "Shape Type:", value: properties.shapeType }); + index++; + array.push({ label: "Compound Shape URL:", value: properties.compoundShapeURL }); index++; array.push({ label: "Animation URL:", value: properties.animationURL }); index++; @@ -284,7 +286,8 @@ EntityPropertyDialogBox = (function () { properties.locked = array[index++].value; if (properties.type == "Model") { properties.modelURL = array[index++].value; - properties.collisionModelURL = array[index++].value; + properties.shapeType = array[index++].value; + properties.compoundShapeURL = array[index++].value; properties.animationURL = array[index++].value; var newAnimationIsPlaying = array[index++].value; diff --git a/examples/libraries/lightOverlayManager.js b/examples/libraries/lightOverlayManager.js index 8032d77c49..a140461a27 100644 --- a/examples/libraries/lightOverlayManager.js +++ b/examples/libraries/lightOverlayManager.js @@ -122,7 +122,7 @@ LightOverlayManager = function() { Entities.clearingEntities.connect(clearEntities); // Add existing entities - var ids = Entities.findEntities(MyAvatar.position, 100); + var ids = Entities.findEntities(MyAvatar.position, 64000); for (var i = 0; i < ids.length; i++) { addEntity(ids[i]); } diff --git a/examples/libraries/zoneOverlayManager.js b/examples/libraries/zoneOverlayManager.js new file mode 100644 index 0000000000..aac3af119b --- /dev/null +++ b/examples/libraries/zoneOverlayManager.js @@ -0,0 +1,146 @@ +ZoneOverlayManager = function(isEntityFunc, entityAddedFunc, entityRemovedFunc, entityMovedFunc) { + var self = this; + + var visible = false; + + // List of all created overlays + var allOverlays = []; + + // List of overlays not currently being used + var unusedOverlays = []; + + // Map from EntityItemID.id to overlay id + var entityOverlays = {}; + + // Map from EntityItemID.id to EntityItemID object + var entityIDs = {}; + + this.updatePositions = function(ids) { + for (var id in entityIDs) { + var entityID = entityIDs[id]; + var properties = Entities.getEntityProperties(entityID); + Overlays.editOverlay(entityOverlays[entityID.id].solid, { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions, + }); + Overlays.editOverlay(entityOverlays[entityID.id].outline, { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions, + }); + } + }; + + this.setVisible = function(isVisible) { + if (visible != isVisible) { + visible = isVisible; + for (var id in entityOverlays) { + Overlays.editOverlay(entityOverlays[id].solid, { visible: visible }); + Overlays.editOverlay(entityOverlays[id].outline, { visible: visible }); + } + } + }; + + // Allocate or get an unused overlay + function getOverlay() { + if (unusedOverlays.length == 0) { + var overlay = Overlays.addOverlay("cube", { + }); + allOverlays.push(overlay); + } else { + var overlay = unusedOverlays.pop(); + }; + return overlay; + } + + function releaseOverlay(overlay) { + unusedOverlays.push(overlay); + Overlays.editOverlay(overlay, { visible: false }); + } + + function addEntity(entityID) { + var properties = Entities.getEntityProperties(entityID); + if (properties.type == "Zone" && !(entityID.id in entityOverlays)) { + var overlaySolid = getOverlay(); + var overlayOutline = getOverlay(); + + entityOverlays[entityID.id] = { + solid: overlaySolid, + outline: overlayOutline, + } + entityIDs[entityID.id] = entityID; + + var color = { + red: Math.round(Math.random() * 255), + green: Math.round(Math.random() * 255), + blue: Math.round(Math.random() * 255) + }; + Overlays.editOverlay(overlaySolid, { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions, + visible: visible, + solid: true, + alpha: 0.1, + color: color, + ignoreRayIntersection: true, + }); + Overlays.editOverlay(overlayOutline, { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions, + visible: visible, + solid: false, + dashed: false, + lineWidth: 2.0, + alpha: 1.0, + color: color, + ignoreRayIntersection: true, + }); + } + } + + function deleteEntity(entityID) { + if (entityID.id in entityOverlays) { + releaseOverlay(entityOverlays[entityID.id].outline); + releaseOverlay(entityOverlays[entityID.id].solid); + delete entityIDs[entityID.id]; + delete entityOverlays[entityID.id]; + } + } + + function changeEntityID(oldEntityID, newEntityID) { + entityOverlays[newEntityID.id] = entityOverlays[oldEntityID.id]; + entityIDs[newEntityID.id] = newEntityID; + + delete entityOverlays[oldEntityID.id]; + delete entityIDs[oldEntityID.id]; + } + + function clearEntities() { + for (var id in entityOverlays) { + releaseOverlay(entityOverlays[id].outline); + releaseOverlay(entityOverlays[id].solid); + } + entityOverlays = {}; + entityIDs = {}; + } + + Entities.addingEntity.connect(addEntity); + Entities.changingEntityID.connect(changeEntityID); + Entities.deletingEntity.connect(deleteEntity); + Entities.clearingEntities.connect(clearEntities); + + // Add existing entities + var ids = Entities.findEntities(MyAvatar.position, 64000); + for (var i = 0; i < ids.length; i++) { + addEntity(ids[i]); + } + + Script.scriptEnding.connect(function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + }); +}; diff --git a/examples/look.js b/examples/look.js index 6bba57e3ad..7adcd054c2 100644 --- a/examples/look.js +++ b/examples/look.js @@ -31,7 +31,9 @@ var yawFromTouch = 0; var pitchFromTouch = 0; // Touch Data +var TIME_BEFORE_GENERATED_END_TOUCH_EVENT = 50; // ms var startedTouching = false; +var lastTouchEvent = 0; var lastMouseX = 0; var lastMouseY = 0; var yawFromMouse = 0; @@ -80,11 +82,17 @@ function touchBeginEvent(event) { yawFromTouch = 0; pitchFromTouch = 0; startedTouching = true; + var d = new Date(); + lastTouchEvent = d.getTime(); } function touchEndEvent(event) { if (wantDebugging) { - print("touchEndEvent event.x,y=" + event.x + ", " + event.y); + if (event) { + print("touchEndEvent event.x,y=" + event.x + ", " + event.y); + } else { + print("touchEndEvent generated"); + } } startedTouching = false; } @@ -96,16 +104,17 @@ function touchUpdateEvent(event) { } if (!startedTouching) { - // handle Qt 5.4.x bug where we get touch update without a touch begin event - startedTouching = true; - lastTouchX = event.x; - lastTouchY = event.y; + // handle Qt 5.4.x bug where we get touch update without a touch begin event + touchBeginEvent(event); + return; } yawFromTouch += ((event.x - lastTouchX) * TOUCH_YAW_SCALE * FIXED_TOUCH_TIMESTEP); pitchFromTouch += ((event.y - lastTouchY) * TOUCH_PITCH_SCALE * FIXED_TOUCH_TIMESTEP); lastTouchX = event.x; lastTouchY = event.y; + var d = new Date(); + lastTouchEvent = d.getTime(); } @@ -113,6 +122,14 @@ function update(deltaTime) { if (wantDebugging) { print("update()..."); } + + if (startedTouching) { + var d = new Date(); + var sinceLastTouch = d.getTime() - lastTouchEvent; + if (sinceLastTouch > TIME_BEFORE_GENERATED_END_TOUCH_EVENT) { + touchEndEvent(); + } + } if (yawFromTouch != 0 || yawFromMouse != 0) { var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollRadians(0, yawFromTouch + yawFromMouse, 0)); diff --git a/examples/lotsoBlocks.js b/examples/lotsoBlocks.js new file mode 100644 index 0000000000..63ed774d2d --- /dev/null +++ b/examples/lotsoBlocks.js @@ -0,0 +1,63 @@ +var NUM_BLOCKS = 200; +var size; +var SPAWN_RANGE = 10; +var boxes = []; +var basePosition, avatarRot; +var isAssignmentScript = false; +if(isAssignmentScript){ + basePosition = {x: 8000, y: 8000, z: 8000}; +} +else { + avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0); + basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(SPAWN_RANGE * 3, Quat.getFront(avatarRot))); +} +basePosition.y -= SPAWN_RANGE; + +var ground = Entities.addEntity({ + type: "Model", + modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/woodFloor.fbx", + dimensions: { + x: 100, + y: 2, + z: 100 + }, + position: basePosition, + shapeType: 'box' +}); + + +basePosition.y += SPAWN_RANGE + 2; +for (var i = 0; i < NUM_BLOCKS; i++) { + size = randFloat(-.2, 0.7); + boxes.push(Entities.addEntity({ + type: 'Box', + dimensions: { + x: size, + y: size, + z: size + }, + position: { + x: basePosition.x + randFloat(-SPAWN_RANGE, SPAWN_RANGE), + y: basePosition.y - randFloat(-SPAWN_RANGE, SPAWN_RANGE), + z: basePosition.z + randFloat(-SPAWN_RANGE, SPAWN_RANGE) + }, + color: {red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255}, + collisionsWillMove: true, + gravity: {x: 0, y: 0, z: 0} + })); +} + + + +function cleanup() { + Entities.deleteEntity(ground); + boxes.forEach(function(box){ + Entities.deleteEntity(box); + }); +} + +Script.scriptEnding.connect(cleanup); + +function randFloat(low, high) { + return low + Math.random() * ( high - low ); +} \ No newline at end of file diff --git a/examples/utilities/tools/cookies.js b/examples/utilities/tools/cookies.js index 205b19c9f9..0182bf8db0 100755 --- a/examples/utilities/tools/cookies.js +++ b/examples/utilities/tools/cookies.js @@ -12,6 +12,129 @@ // The Slider class Slider = function(x,y,width,thumbSize) { + this.background = Overlays.addOverlay("text", { + backgroundColor: { red: 125, green: 125, blue: 255 }, + x: x, + y: y, + width: width, + height: thumbSize, + alpha: 1.0, + backgroundAlpha: 0.5, + visible: true + }); + this.thumb = Overlays.addOverlay("text", { + backgroundColor: { red: 255, green: 255, blue: 255 }, + x: x, + y: y, + width: thumbSize, + height: thumbSize, + alpha: 1.0, + backgroundAlpha: 1.0, + visible: true + }); + + + this.thumbSize = thumbSize; + this.thumbHalfSize = 0.5 * thumbSize; + + this.minThumbX = x + this.thumbHalfSize; + this.maxThumbX = x + width - this.thumbHalfSize; + this.thumbX = this.minThumbX; + + this.minValue = 0.0; + this.maxValue = 1.0; + + this.clickOffsetX = 0; + this.isMoving = false; + + this.updateThumb = function() { + thumbTruePos = this.thumbX - 0.5 * this.thumbSize; + Overlays.editOverlay(this.thumb, { x: thumbTruePos } ); + }; + + this.isClickableOverlayItem = function(item) { + return (item == this.thumb) || (item == this.background); + }; + + this.onMouseMoveEvent = function(event) { + if (this.isMoving) { + newThumbX = event.x - this.clickOffsetX; + if (newThumbX < this.minThumbX) { + newThumbX = this.minThumbX; + } + if (newThumbX > this.maxThumbX) { + newThumbX = this.maxThumbX; + } + this.thumbX = newThumbX; + this.updateThumb(); + this.onValueChanged(this.getValue()); + } + }; + + this.onMousePressEvent = function(event, clickedOverlay) { + if (!this.isClickableOverlayItem(clickedOverlay)) { + this.isMoving = false; + return; + } + this.isMoving = true; + var clickOffset = event.x - this.thumbX; + if ((clickOffset > -this.thumbHalfSize) && (clickOffset < this.thumbHalfSize)) { + this.clickOffsetX = clickOffset; + } else { + this.clickOffsetX = 0; + this.thumbX = event.x; + this.updateThumb(); + this.onValueChanged(this.getValue()); + } + + }; + + this.onMouseReleaseEvent = function(event) { + this.isMoving = false; + }; + + // Public members: + + this.setNormalizedValue = function(value) { + if (value < 0.0) { + this.thumbX = this.minThumbX; + } else if (value > 1.0) { + this.thumbX = this.maxThumbX; + } else { + this.thumbX = value * (this.maxThumbX - this.minThumbX) + this.minThumbX; + } + this.updateThumb(); + }; + this.getNormalizedValue = function() { + return (this.thumbX - this.minThumbX) / (this.maxThumbX - this.minThumbX); + }; + + this.setValue = function(value) { + var normValue = (value - this.minValue) / (this.maxValue - this.minValue); + this.setNormalizedValue(normValue); + }; + + this.getValue = function() { + return this.getNormalizedValue() * (this.maxValue - this.minValue) + this.minValue; + }; + + this.onValueChanged = function(value) {}; + + this.destroy = function() { + Overlays.deleteOverlay(this.background); + Overlays.deleteOverlay(this.thumb); + }; + + this.setThumbColor = function(color) { + Overlays.editOverlay(this.thumb, {backgroundColor: { red: color.x*255, green: color.y*255, blue: color.z*255 }}); + }; + this.setBackgroundColor = function(color) { + Overlays.editOverlay(this.background, {backgroundColor: { red: color.x*255, green: color.y*255, blue: color.z*255 }}); + }; +} + +// The Checkbox class +Checkbox = function(x,y,width,thumbSize) { this.thumb = Overlays.addOverlay("text", { backgroundColor: { red: 255, green: 255, blue: 255 }, @@ -52,6 +175,10 @@ Slider = function(x,y,width,thumbSize) { Overlays.editOverlay(this.thumb, { x: thumbTruePos } ); }; + this.isClickableOverlayItem = function(item) { + return item == this.background; + }; + this.onMouseMoveEvent = function(event) { if (this.isMoving) { newThumbX = event.x - this.clickOffsetX; @@ -67,7 +194,11 @@ Slider = function(x,y,width,thumbSize) { } }; - this.onMousePressEvent = function(event) { + this.onMousePressEvent = function(event, clickedOverlay) { + if (this.background != clickedOverlay) { + this.isMoving = false; + return; + } this.isMoving = true; var clickOffset = event.x - this.thumbX; if ((clickOffset > -this.thumbHalfSize) && (clickOffset < this.thumbHalfSize)) { @@ -118,6 +249,167 @@ Slider = function(x,y,width,thumbSize) { }; } +// The ColorBox class +ColorBox = function(x,y,width,thumbSize) { + var self = this; + + var slideHeight = thumbSize / 3; + var sliderWidth = width; + this.red = new Slider(x, y, width, slideHeight); + this.green = new Slider(x, y + slideHeight, width, slideHeight); + this.blue = new Slider(x, y + 2 * slideHeight, width, slideHeight); + this.red.setBackgroundColor({x: 1, y: 0, z: 0}); + this.green.setBackgroundColor({x: 0, y: 1, z: 0}); + this.blue.setBackgroundColor({x: 0, y: 0, z: 1}); + + this.isClickableOverlayItem = function(item) { + return this.red.isClickableOverlayItem(item) + || this.green.isClickableOverlayItem(item) + || this.blue.isClickableOverlayItem(item); + }; + + this.onMouseMoveEvent = function(event) { + this.red.onMouseMoveEvent(event); + this.green.onMouseMoveEvent(event); + this.blue.onMouseMoveEvent(event); + }; + + this.onMousePressEvent = function(event, clickedOverlay) { + this.red.onMousePressEvent(event, clickedOverlay); + if (this.red.isMoving) { + return; + } + + this.green.onMousePressEvent(event, clickedOverlay); + if (this.green.isMoving) { + return; + } + + this.blue.onMousePressEvent(event, clickedOverlay); + }; + + this.onMouseReleaseEvent = function(event) { + this.red.onMouseReleaseEvent(event); + this.green.onMouseReleaseEvent(event); + this.blue.onMouseReleaseEvent(event); + }; + + this.setterFromWidget = function(value) { + var color = self.getValue(); + self.onValueChanged(color); + self.updateRGBSliders(color); + }; + + this.red.onValueChanged = this.setterFromWidget; + this.green.onValueChanged = this.setterFromWidget; + this.blue.onValueChanged = this.setterFromWidget; + + this.updateRGBSliders = function(color) { + this.red.setThumbColor({x: color.x, y: 0, z: 0}); + this.green.setThumbColor({x: 0, y: color.y, z: 0}); + this.blue.setThumbColor({x: 0, y: 0, z: color.z}); + }; + + // Public members: + this.setValue = function(value) { + this.red.setValue(value.x); + this.green.setValue(value.y); + this.blue.setValue(value.z); + this.updateRGBSliders(value); + }; + + this.getValue = function() { + var value = {x:this.red.getValue(), y:this.green.getValue(),z:this.blue.getValue()}; + return value; + }; + + this.destroy = function() { + this.red.destroy(); + this.green.destroy(); + this.blue.destroy(); + }; + + this.onValueChanged = function(value) {}; +} + +// The DirectionBox class +DirectionBox = function(x,y,width,thumbSize) { + var self = this; + + var slideHeight = thumbSize / 2; + var sliderWidth = width; + this.yaw = new Slider(x, y, width, slideHeight); + this.pitch = new Slider(x, y + slideHeight, width, slideHeight); + + this.yaw.setThumbColor({x: 1, y: 0, z: 0}); + this.yaw.minValue = -180; + this.yaw.maxValue = +180; + + this.pitch.setThumbColor({x: 0, y: 0, z: 1}); + this.pitch.minValue = -1; + this.pitch.maxValue = +1; + + this.isClickableOverlayItem = function(item) { + return this.yaw.isClickableOverlayItem(item) + || this.pitch.isClickableOverlayItem(item); + }; + + this.onMouseMoveEvent = function(event) { + this.yaw.onMouseMoveEvent(event); + this.pitch.onMouseMoveEvent(event); + }; + + this.onMousePressEvent = function(event, clickedOverlay) { + this.yaw.onMousePressEvent(event, clickedOverlay); + if (this.yaw.isMoving) { + return; + } + this.pitch.onMousePressEvent(event, clickedOverlay); + }; + + this.onMouseReleaseEvent = function(event) { + this.yaw.onMouseReleaseEvent(event); + this.pitch.onMouseReleaseEvent(event); + }; + + this.setterFromWidget = function(value) { + var yawPitch = self.getValue(); + self.onValueChanged(yawPitch); + }; + + this.yaw.onValueChanged = this.setterFromWidget; + this.pitch.onValueChanged = this.setterFromWidget; + + // Public members: + this.setValue = function(direction) { + var flatXZ = Math.sqrt(direction.x * direction.x + direction.z * direction.z); + if (flatXZ > 0.0) { + var flatX = direction.x / flatXZ; + var flatZ = direction.z / flatXZ; + var yaw = Math.acos(flatX) * 180 / Math.PI; + if (flatZ < 0) { + yaw = -yaw; + } + this.yaw.setValue(yaw); + } + this.pitch.setValue(direction.y); + }; + + this.getValue = function() { + var dirZ = this.pitch.getValue(); + var yaw = this.yaw.getValue() * Math.PI / 180; + var cosY = Math.sqrt(1 - dirZ*dirZ); + var value = {x:cosY * Math.cos(yaw), y:dirZ, z: cosY * Math.sin(yaw)}; + return value; + }; + + this.destroy = function() { + this.yaw.destroy(); + this.pitch.destroy(); + }; + + this.onValueChanged = function(value) {}; +} var textFontSize = 16; @@ -167,7 +459,12 @@ function PanelItem(name, setter, getter, displayer, x, y, textWidth, valueWidth, }; this.setterFromWidget = function(value) { setter(value); - Overlays.editOverlay(this.value, {text: this.displayer(getter())}); + // ANd loop back the value after the final setter has been called + var value = getter(); + if (this.widget) { + this.widget.setValue(value); + } + Overlays.editOverlay(this.value, {text: this.displayer(value)}); }; @@ -219,9 +516,9 @@ Panel = function(x, y) { for (var i in this.items) { var widget = this.items[i].widget; - if (clickedOverlay == widget.background) { + if (widget.isClickableOverlayItem(clickedOverlay)) { this.activeWidget = widget; - this.activeWidget.onMousePressEvent(event); + this.activeWidget.onMousePressEvent(event, clickedOverlay); // print("clicked... widget=" + i); break; } @@ -237,21 +534,63 @@ Panel = function(x, y) { this.newSlider = function(name, minValue, maxValue, setValue, getValue, displayValue) { - var sliderItem = new PanelItem(name, setValue, getValue, displayValue, this.x, this.nextY, textWidth, valueWidth, rawHeight); + var item = new PanelItem(name, setValue, getValue, displayValue, this.x, this.nextY, textWidth, valueWidth, rawHeight); var slider = new Slider(this.widgetX, this.nextY, widgetWidth, rawHeight); slider.minValue = minValue; slider.maxValue = maxValue; - slider.onValueChanged = function(value) { sliderItem.setterFromWidget(value); }; - sliderItem.widget = slider; - sliderItem.setter(getValue()); - this.items[name] = sliderItem; + item.widget = slider; + item.widget.onValueChanged = function(value) { item.setterFromWidget(value); }; + item.setter(getValue()); + this.items[name] = item; this.nextY += rawYDelta; // print("created Item... slider=" + name); }; + this.newCheckbox = function(name, setValue, getValue, displayValue) { + + var item = new PanelItem(name, setValue, getValue, displayValue, this.x, this.nextY, textWidth, valueWidth, rawHeight); + + var checkbox = new Checkbox(this.widgetX, this.nextY, widgetWidth, rawHeight); + + item.widget = checkbox; + item.widget.onValueChanged = function(value) { item.setterFromWidget(value); }; + item.setter(getValue()); + this.items[name] = item; + this.nextY += rawYDelta; + // print("created Item... slider=" + name); + }; + + this.newColorBox = function(name, setValue, getValue, displayValue) { + + var item = new PanelItem(name, setValue, getValue, displayValue, this.x, this.nextY, textWidth, valueWidth, rawHeight); + + var colorBox = new ColorBox(this.widgetX, this.nextY, widgetWidth, rawHeight); + + item.widget = colorBox; + item.widget.onValueChanged = function(value) { item.setterFromWidget(value); }; + item.setter(getValue()); + this.items[name] = item; + this.nextY += rawYDelta; + // print("created Item... colorBox=" + name); + }; + + this.newDirectionBox= function(name, setValue, getValue, displayValue) { + + var item = new PanelItem(name, setValue, getValue, displayValue, this.x, this.nextY, textWidth, valueWidth, rawHeight); + + var directionBox = new DirectionBox(this.widgetX, this.nextY, widgetWidth, rawHeight); + + item.widget = directionBox; + item.widget.onValueChanged = function(value) { item.setterFromWidget(value); }; + item.setter(getValue()); + this.items[name] = item; + this.nextY += rawYDelta; + // print("created Item... directionBox=" + name); + }; + this.destroy = function() { for (var i in this.items) { this.items[i].destroy(); @@ -273,6 +612,15 @@ Panel = function(x, y) { } return null; } + + this.update = function(name) { + var item = this.items[name]; + if (item != null) { + return item.setter(item.getter()); + } + return null; + } + }; diff --git a/examples/utilities/tools/developerMenuItems.js b/examples/utilities/tools/developerMenuItems.js index a691031131..ace2b032e2 100644 --- a/examples/utilities/tools/developerMenuItems.js +++ b/examples/utilities/tools/developerMenuItems.js @@ -12,17 +12,29 @@ // var createdRenderMenu = false; +var createdGeneratedAudioMenu = false; +var createdStereoInputMenuItem = false; -var ENTITIES_MENU = "Developer > Entities"; +var DEVELOPER_MENU = "Developer"; + +var ENTITIES_MENU = DEVELOPER_MENU + " > Entities"; var COLLISION_UPDATES_TO_SERVER = "Don't send collision updates to server"; -var RENDER_MENU = "Developer > Render"; +var RENDER_MENU = DEVELOPER_MENU + " > Render"; var ENTITIES_ITEM = "Entities"; var AVATARS_ITEM = "Avatars"; +var AUDIO_MENU = DEVELOPER_MENU + " > Audio"; +var AUDIO_SOURCE_INJECT = "Generated Audio"; +var AUDIO_SOURCE_MENU = AUDIO_MENU + " > Generated Audio Source"; +var AUDIO_SOURCE_PINK_NOISE = "Pink Noise"; +var AUDIO_SOURCE_SINE_440 = "Sine 440hz"; +var AUDIO_STEREO_INPUT = "Stereo Input"; + + function setupMenus() { - if (!Menu.menuExists("Developer")) { - Menu.addMenu("Developer"); + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); } if (!Menu.menuExists(ENTITIES_MENU)) { Menu.addMenu(ENTITIES_MENU); @@ -54,6 +66,24 @@ function setupMenus() { if (!Menu.menuItemExists(RENDER_MENU, AVATARS_ITEM)) { Menu.addMenuItem({ menuName: RENDER_MENU, menuItemName: AVATARS_ITEM, isCheckable: true, isChecked: Scene.shouldRenderAvatars }) } + + + if (!Menu.menuExists(AUDIO_MENU)) { + Menu.addMenu(AUDIO_MENU); + } + if (!Menu.menuItemExists(AUDIO_MENU, AUDIO_SOURCE_INJECT)) { + Menu.addMenuItem({ menuName: AUDIO_MENU, menuItemName: AUDIO_SOURCE_INJECT, isCheckable: true, isChecked: false }); + Menu.addMenu(AUDIO_SOURCE_MENU); + Menu.addMenuItem({ menuName: AUDIO_SOURCE_MENU, menuItemName: AUDIO_SOURCE_PINK_NOISE, isCheckable: true, isChecked: false }); + Menu.addMenuItem({ menuName: AUDIO_SOURCE_MENU, menuItemName: AUDIO_SOURCE_SINE_440, isCheckable: true, isChecked: false }); + Menu.setIsOptionChecked(AUDIO_SOURCE_PINK_NOISE, true); + Audio.selectPinkNoise(); + createdGeneratedAudioMenu = true; + } + if (!Menu.menuItemExists(AUDIO_MENU, AUDIO_STEREO_INPUT)) { + Menu.addMenuItem({ menuName: AUDIO_MENU, menuItemName: AUDIO_STEREO_INPUT, isCheckable: true, isChecked: false }); + createdStereoInputMenuItem = true; + } } Menu.menuItemEvent.connect(function (menuItem) { @@ -67,7 +97,17 @@ Menu.menuItemEvent.connect(function (menuItem) { Scene.shouldRenderEntities = Menu.isOptionChecked(ENTITIES_ITEM); } else if (menuItem == AVATARS_ITEM) { Scene.shouldRenderAvatars = Menu.isOptionChecked(AVATARS_ITEM); - } + } else if (menuItem == AUDIO_SOURCE_INJECT && !createdGeneratedAudioMenu) { + Audio.injectGeneratedNoise(Menu.isOptionChecked(AUDIO_SOURCE_INJECT)); + } else if (menuItem == AUDIO_SOURCE_PINK_NOISE && !createdGeneratedAudioMenu) { + Audio.selectPinkNoise(); + Menu.setIsOptionChecked(AUDIO_SOURCE_SINE_440, false); + } else if (menuItem == AUDIO_SOURCE_SINE_440 && !createdGeneratedAudioMenu) { + Audio.selectSine440(); + Menu.setIsOptionChecked(AUDIO_SOURCE_PINK_NOISE, false); + } else if (menuItem == AUDIO_STEREO_INPUT) { + Audio.setStereoInput(Menu.isOptionChecked(AUDIO_STEREO_INPUT)) + } }); Scene.shouldRenderAvatarsChanged.connect(function(shouldRenderAvatars) { @@ -87,6 +127,16 @@ function scriptEnding() { Menu.removeMenuItem(RENDER_MENU, ENTITIES_ITEM); Menu.removeMenuItem(RENDER_MENU, AVATARS_ITEM); } + + if (createdGeneratedAudioMenu) { + Audio.injectGeneratedNoise(false); + Menu.removeMenuItem(AUDIO_MENU, AUDIO_SOURCE_INJECT); + Menu.removeMenu(AUDIO_SOURCE_MENU); + } + + if (createdStereoInputMenuItem) { + Menu.removeMenuItem(AUDIO_MENU, AUDIO_STEREO_INPUT); + } } setupMenus(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f4f390607b..9e282f4725 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -54,7 +54,7 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Script Svg WebKitWidgets) +find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) @@ -88,18 +88,15 @@ if (APPLE) # set where in the bundle to put the resources file SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - # grab the directories in resources and put them in the right spot in Resources - file(GLOB RESOURCE_SUBDIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/resources" "${CMAKE_CURRENT_SOURCE_DIR}/resources/*") - foreach(DIR ${RESOURCE_SUBDIRS}) - if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/resources/${DIR}") - FILE(GLOB DIR_CONTENTS "resources/${DIR}/*") - SET_SOURCE_FILES_PROPERTIES(${DIR_CONTENTS} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${DIR}") + set(DISCOVERED_RESOURCES "") - SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${DIR_CONTENTS}") - endif() - endforeach() + # use the add_resources_to_os_x_bundle macro to recurse into resources + add_resources_to_os_x_bundle("${CMAKE_CURRENT_SOURCE_DIR}/resources") - SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME}") + # append the discovered resources to our list of interface sources + list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES}) + + set(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME}") endif() # create the executable, make it a bundle on OS X @@ -128,7 +125,7 @@ target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) # link required hifi libraries link_hifi_libraries(shared octree environment gpu model fbx networking entities avatars audio audio-client animation script-engine physics - render-utils entities-renderer) + render-utils entities-renderer ui) add_dependency_external_projects(sdl2) @@ -170,15 +167,6 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) endif () endforeach() -# special APPLE modifications for Visage library -if (VISAGE_FOUND AND NOT DISABLE_VISAGE AND APPLE) - add_definitions(-DMAC_OS_X) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-comment") - find_library(AVFoundation AVFoundation) - find_library(CoreMedia CoreMedia) - target_link_libraries(${TARGET_NAME} ${AVFoundation} ${CoreMedia} ${NEW_STD_LIBRARY}) -endif () - # special OS X modifications for RtMidi library if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI AND APPLE) find_library(CoreMIDI CoreMIDI) diff --git a/interface/external/visage/readme.txt b/interface/external/visage/readme.txt deleted file mode 100644 index 3aff1039dc..0000000000 --- a/interface/external/visage/readme.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Instructions for adding the Visage driver to Interface -Andrzej Kapolka, February 11, 2014 - -1. Copy the Visage sdk folders (lib, include, dependencies) into the interface/external/visage folder. - This readme.txt should be there as well. - -2. Copy the contents of the Visage configuration data folder (visageSDK-MacOS/Samples/MacOSX/data/) to - interface/resources/visage (i.e., so that interface/resources/visage/candide3.wfm is accessible) - -3. Copy the Visage license file to interface/resources/visage/license.vlc. - -4. Delete your build directory, run cmake and build, and you should be all set. - diff --git a/interface/resources/fonts/fontawesome-webfont.ttf b/interface/resources/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000..ed9372f8ea Binary files /dev/null and b/interface/resources/fonts/fontawesome-webfont.ttf differ diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml new file mode 100644 index 0000000000..e12452472a --- /dev/null +++ b/interface/resources/qml/AddressBarDialog.qml @@ -0,0 +1,102 @@ +import Hifi 1.0 +import QtQuick 2.3 +import "controls" +import "styles" + +Dialog { + id: root + HifiConstants { id: hifi } + + title: "Go to..." + objectName: "AddressBarDialog" + contentImplicitWidth: addressBarDialog.implicitWidth + contentImplicitHeight: addressBarDialog.implicitHeight + destroyOnCloseButton: false + + + onVisibleChanged: { + if (!visible) { + reset(); + } + } + + onEnabledChanged: { + if (enabled) { + addressLine.forceActiveFocus(); + } + } + onParentChanged: { + if (enabled && visible) { + addressLine.forceActiveFocus(); + } + } + + function reset() { + addressLine.text = "" + goButton.source = "../images/address-bar-submit.svg" + } + + AddressBarDialog { + id: addressBarDialog + // The client area + x: root.clientX + y: root.clientY + implicitWidth: 512 + implicitHeight: border.height + hifi.layout.spacing * 4 + + + Border { + id: border + height: 64 + anchors.left: parent.left + anchors.leftMargin: hifi.layout.spacing * 2 + anchors.right: goButton.left + anchors.rightMargin: hifi.layout.spacing + anchors.verticalCenter: parent.verticalCenter + TextInput { + id: addressLine + anchors.fill: parent + helperText: "domain, location, @user, /x,y,z" + anchors.margins: hifi.layout.spacing + onAccepted: { + event.accepted + addressBarDialog.loadAddress(addressLine.text) + } + } + } + + Image { + id: goButton + width: 32 + height: 32 + anchors.right: parent.right + anchors.rightMargin: hifi.layout.spacing * 2 + source: "../images/address-bar-submit.svg" + anchors.verticalCenter: parent.verticalCenter + + MouseArea { + anchors.fill: parent + onClicked: { + parent.source = "../images/address-bar-submit-active.svg" + addressBarDialog.loadAddress(addressLine.text) + } + } + } + } + + Keys.onEscapePressed: { + enabled = false; + } + + function toggleOrGo() { + if (addressLine.text == "") { + enabled = false + } else { + addressBarDialog.loadAddress(addressLine.text) + } + } + + Keys.onReturnPressed: toggleOrGo() + Keys.onEnterPressed: toggleOrGo() +} + diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml new file mode 100644 index 0000000000..a439f9114c --- /dev/null +++ b/interface/resources/qml/Browser.qml @@ -0,0 +1,30 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtWebKit 3.0 +import "controls" + +Dialog { + title: "Browser Window" + id: testDialog + objectName: "Browser" + width: 1280 + height: 720 + + Item { + id: clientArea + // The client area + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + + ScrollView { + anchors.fill: parent + WebView { + id: webview + url: "http://slashdot.org" + anchors.fill: parent + } + } + + } +} diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml new file mode 100644 index 0000000000..5653dfc7a1 --- /dev/null +++ b/interface/resources/qml/LoginDialog.qml @@ -0,0 +1,198 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls.Styles 1.3 +import "controls" +import "styles" + +Dialog { + HifiConstants { id: hifi } + title: "Login" + objectName: "LoginDialog" + height: 512 + width: 384 + + onVisibleChanged: { + if (!visible) { + reset() + } + } + + onEnabledChanged: { + if (enabled) { + username.forceActiveFocus(); + } + } + + function reset() { + username.text = "" + password.text = "" + loginDialog.statusText = "" + } + + LoginDialog { + id: loginDialog + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + Column { + anchors.topMargin: 8 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.top: parent.top + spacing: 8 + + Image { + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + width: 64 + source: "../images/hifi-logo.svg" + } + + Border { + width: 304 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + TextInput { + id: username + anchors.fill: parent + helperText: "Username or Email" + anchors.margins: 8 + KeyNavigation.tab: password + KeyNavigation.backtab: password + } + } + + Border { + width: 304 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + TextInput { + id: password + anchors.fill: parent + echoMode: TextInput.Password + helperText: "Password" + anchors.margins: 8 + KeyNavigation.tab: username + KeyNavigation.backtab: username + onFocusChanged: { + if (password.focus) { + password.selectAll() + } + } + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + textFormat: Text.StyledText + width: parent.width + height: 96 + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: loginDialog.statusText + } + } + + Column { + anchors.bottomMargin: 5 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.bottom: parent.bottom + + Rectangle { + width: 192 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + color: hifi.colors.hifiBlue + border.width: 0 + radius: 10 + + MouseArea { + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + onClicked: { + loginDialog.login(username.text, password.text) + } + } + + Row { + anchors.centerIn: parent + anchors.verticalCenter: parent.verticalCenter + spacing: 8 + Image { + id: loginIcon + height: 32 + width: 32 + source: "../images/login.svg" + } + Text { + text: "Login" + color: "white" + width: 64 + height: parent.height + } + } + + } + + Text { + width: parent.width + height: 24 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text:"Create Account" + font.pointSize: 12 + font.bold: true + color: hifi.colors.hifiBlue + + MouseArea { + anchors.fill: parent + onClicked: { + loginDialog.openUrl(loginDialog.rootUrl + "/signup") + } + } + } + + Text { + width: parent.width + height: 24 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: 12 + text: "Recover Password" + color: hifi.colors.hifiBlue + + MouseArea { + anchors.fill: parent + onClicked: { + loginDialog.openUrl(loginDialog.rootUrl + "/users/password/new") + } + } + } + } + } + Keys.onPressed: { + switch(event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + if (username.activeFocus) { + event.accepted = true + password.forceActiveFocus() + } else if (password.activeFocus) { + event.accepted = true + if (username.text == "") { + username.forceActiveFocus() + } else { + loginDialog.login(username.text, password.text) + } + } + break; + } + } +} diff --git a/interface/resources/qml/MarketplaceDialog.qml b/interface/resources/qml/MarketplaceDialog.qml new file mode 100644 index 0000000000..58bb3e6183 --- /dev/null +++ b/interface/resources/qml/MarketplaceDialog.qml @@ -0,0 +1,50 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.3 +import QtWebKit 3.0 +import "controls" + +Dialog { + title: "Test Dlg" + id: testDialog + objectName: "Browser" + width: 720 + height: 720 + resizable: true + + MarketplaceDialog { + id: marketplaceDialog + } + + Item { + id: clientArea + // The client area + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + + + ScrollView { + anchors.fill: parent + WebView { + objectName: "WebView" + id: webview + url: "https://metaverse.highfidelity.com/marketplace" + anchors.fill: parent + onNavigationRequested: { + console.log(request.url) + if (!marketplaceDialog.navigationRequested(request.url)) { + console.log("Application absorbed the request") + request.action = WebView.IgnoreRequest; + return; + } + console.log("Application passed on the request") + request.action = WebView.AcceptRequest; + return; + } + } + } + + } +} diff --git a/interface/resources/qml/MessageDialog.qml b/interface/resources/qml/MessageDialog.qml new file mode 100644 index 0000000000..dd410b8070 --- /dev/null +++ b/interface/resources/qml/MessageDialog.qml @@ -0,0 +1,333 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 +import "controls" +import "styles" + +Dialog { + id: root + HifiConstants { id: hifi } + property real spacing: hifi.layout.spacing + property real outerSpacing: hifi.layout.spacing * 2 + + destroyOnCloseButton: true + destroyOnInvisible: true + contentImplicitWidth: content.implicitWidth + contentImplicitHeight: content.implicitHeight + + Component.onCompleted: { + enabled = true + } + + onParentChanged: { + if (visible && enabled) { + forceActiveFocus(); + } + } + + Hifi.MessageDialog { + id: content + clip: true + + x: root.clientX + y: root.clientY + implicitHeight: contentColumn.implicitHeight + outerSpacing * 2 + implicitWidth: mainText.implicitWidth + outerSpacing * 2 + + Component.onCompleted: { + root.title = title + } + + onTitleChanged: { + root.title = title + } + + Column { + anchors.fill: parent + anchors.margins: 8 + id: contentColumn + spacing: root.outerSpacing + + Item { + width: parent.width + height: Math.max(icon.height, mainText.height + informativeText.height + root.spacing) + + FontAwesome { + id: icon + width: content.icon ? 48 : 0 + height: content.icon ? 48 : 0 + visible: content.icon ? true : false + font.pixelSize: 48 + verticalAlignment: Text.AlignTop + horizontalAlignment: Text.AlignLeft + color: iconColor() + text: iconSymbol() + + function iconSymbol() { + switch (content.icon) { + case Hifi.MessageDialog.Information: + return "\uF05A" + case Hifi.MessageDialog.Question: + return "\uF059" + case Hifi.MessageDialog.Warning: + return "\uF071" + case Hifi.MessageDialog.Critical: + return "\uF057" + default: + break; + } + return content.icon; + } + function iconColor() { + switch (content.icon) { + case Hifi.MessageDialog.Information: + case Hifi.MessageDialog.Question: + return "blue" + case Hifi.MessageDialog.Warning: + case Hifi.MessageDialog.Critical: + return "red" + default: + break + } + return "black" + } + } + + Text { + id: mainText + anchors { + left: icon.right + leftMargin: root.spacing + right: parent.right + } + text: content.text + font.pointSize: 14 + font.weight: Font.Bold + wrapMode: Text.WordWrap + } + + Text { + id: informativeText + anchors { + left: icon.right + right: parent.right + top: mainText.bottom + leftMargin: root.spacing + topMargin: root.spacing + } + text: content.informativeText + font.pointSize: 14 + wrapMode: Text.WordWrap + } + } + + + Flow { + id: buttons + spacing: root.spacing + layoutDirection: Qt.RightToLeft + width: parent.width + Button { + id: okButton + text: qsTr("OK") + onClicked: content.click(StandardButton.Ok) + visible: content.standardButtons & StandardButton.Ok + } + Button { + id: openButton + text: qsTr("Open") + onClicked: content.click(StandardButton.Open) + visible: content.standardButtons & StandardButton.Open + } + Button { + id: saveButton + text: qsTr("Save") + onClicked: content.click(StandardButton.Save) + visible: content.standardButtons & StandardButton.Save + } + Button { + id: saveAllButton + text: qsTr("Save All") + onClicked: content.click(StandardButton.SaveAll) + visible: content.standardButtons & StandardButton.SaveAll + } + Button { + id: retryButton + text: qsTr("Retry") + onClicked: content.click(StandardButton.Retry) + visible: content.standardButtons & StandardButton.Retry + } + Button { + id: ignoreButton + text: qsTr("Ignore") + onClicked: content.click(StandardButton.Ignore) + visible: content.standardButtons & StandardButton.Ignore + } + Button { + id: applyButton + text: qsTr("Apply") + onClicked: content.click(StandardButton.Apply) + visible: content.standardButtons & StandardButton.Apply + } + Button { + id: yesButton + text: qsTr("Yes") + onClicked: content.click(StandardButton.Yes) + visible: content.standardButtons & StandardButton.Yes + } + Button { + id: yesAllButton + text: qsTr("Yes to All") + onClicked: content.click(StandardButton.YesToAll) + visible: content.standardButtons & StandardButton.YesToAll + } + Button { + id: noButton + text: qsTr("No") + onClicked: content.click(StandardButton.No) + visible: content.standardButtons & StandardButton.No + } + Button { + id: noAllButton + text: qsTr("No to All") + onClicked: content.click(StandardButton.NoToAll) + visible: content.standardButtons & StandardButton.NoToAll + } + Button { + id: discardButton + text: qsTr("Discard") + onClicked: content.click(StandardButton.Discard) + visible: content.standardButtons & StandardButton.Discard + } + Button { + id: resetButton + text: qsTr("Reset") + onClicked: content.click(StandardButton.Reset) + visible: content.standardButtons & StandardButton.Reset + } + Button { + id: restoreDefaultsButton + text: qsTr("Restore Defaults") + onClicked: content.click(StandardButton.RestoreDefaults) + visible: content.standardButtons & StandardButton.RestoreDefaults + } + Button { + id: cancelButton + text: qsTr("Cancel") + onClicked: content.click(StandardButton.Cancel) + visible: content.standardButtons & StandardButton.Cancel + } + Button { + id: abortButton + text: qsTr("Abort") + onClicked: content.click(StandardButton.Abort) + visible: content.standardButtons & StandardButton.Abort + } + Button { + id: closeButton + text: qsTr("Close") + onClicked: content.click(StandardButton.Close) + visible: content.standardButtons & StandardButton.Close + } + Button { + id: moreButton + text: qsTr("Show Details...") + onClicked: content.state = (content.state === "" ? "expanded" : "") + visible: content.detailedText.length > 0 + } + Button { + id: helpButton + text: qsTr("Help") + onClicked: content.click(StandardButton.Help) + visible: content.standardButtons & StandardButton.Help + } + } + } + + Item { + id: details + width: parent.width + implicitHeight: detailedText.implicitHeight + root.spacing + height: 0 + clip: true + + anchors { + left: parent.left + right: parent.right + top: contentColumn.bottom + topMargin: root.spacing + leftMargin: root.outerSpacing + rightMargin: root.outerSpacing + } + + Flickable { + id: flickable + contentHeight: detailedText.height + anchors.fill: parent + anchors.topMargin: root.spacing + anchors.bottomMargin: root.outerSpacing + TextEdit { + id: detailedText + text: content.detailedText + width: details.width + wrapMode: Text.WordWrap + readOnly: true + selectByMouse: true + } + } + } + + states: [ + State { + name: "expanded" + PropertyChanges { + target: details + height: root.height - contentColumn.height - root.spacing - root.outerSpacing + } + PropertyChanges { + target: content + implicitHeight: contentColumn.implicitHeight + root.spacing * 2 + + detailedText.implicitHeight + root.outerSpacing * 2 + } + PropertyChanges { + target: moreButton + text: qsTr("Hide Details") + } + } + ] + } + + + Keys.onPressed: { + if (event.modifiers === Qt.ControlModifier) + switch (event.key) { + case Qt.Key_A: + event.accepted = true + detailedText.selectAll() + break + case Qt.Key_C: + event.accepted = true + detailedText.copy() + break + case Qt.Key_Period: + if (Qt.platform.os === "osx") { + event.accepted = true + content.reject() + } + break + } else switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + event.accepted = true + content.reject() + break + + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + content.accept() + break + } + } +} diff --git a/interface/resources/qml/Palettes.qml b/interface/resources/qml/Palettes.qml new file mode 100644 index 0000000000..2bdf6eba8b --- /dev/null +++ b/interface/resources/qml/Palettes.qml @@ -0,0 +1,150 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +Rectangle { + color: "teal" + height: 512 + width: 192 + SystemPalette { id: sp; colorGroup: SystemPalette.Active } + SystemPalette { id: spi; colorGroup: SystemPalette.Inactive } + SystemPalette { id: spd; colorGroup: SystemPalette.Disabled } + + Column { + anchors.margins: 8 + anchors.fill: parent + spacing: 8 + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "base" } + Rectangle { height: parent.height; width: 16; color: sp.base } + Rectangle { height: parent.height; width: 16; color: spi.base } + Rectangle { height: parent.height; width: 16; color: spd.base } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "alternateBase" } + Rectangle { height: parent.height; width: 16; color: sp.alternateBase } + Rectangle { height: parent.height; width: 16; color: spi.alternateBase } + Rectangle { height: parent.height; width: 16; color: spd.alternateBase } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "dark" } + Rectangle { height: parent.height; width: 16; color: sp.dark } + Rectangle { height: parent.height; width: 16; color: spi.dark } + Rectangle { height: parent.height; width: 16; color: spd.dark } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "mid" } + Rectangle { height: parent.height; width: 16; color: sp.mid } + Rectangle { height: parent.height; width: 16; color: spi.mid } + Rectangle { height: parent.height; width: 16; color: spd.mid } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "mid light" } + Rectangle { height: parent.height; width: 16; color: sp.midlight } + Rectangle { height: parent.height; width: 16; color: spi.midlight } + Rectangle { height: parent.height; width: 16; color: spd.midlight } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "light" } + Rectangle { height: parent.height; width: 16; color: sp.light} + Rectangle { height: parent.height; width: 16; color: spi.light} + Rectangle { height: parent.height; width: 16; color: spd.light} + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "shadow" } + Rectangle { height: parent.height; width: 16; color: sp.shadow} + Rectangle { height: parent.height; width: 16; color: spi.shadow} + Rectangle { height: parent.height; width: 16; color: spd.shadow} + } + Item { + height: 16 + width:parent.width + } + + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "text" } + Rectangle { height: parent.height; width: 16; color: sp.text } + Rectangle { height: parent.height; width: 16; color: spi.text } + Rectangle { height: parent.height; width: 16; color: spd.text } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "window" } + Rectangle { height: parent.height; width: 16; color: sp.window } + Rectangle { height: parent.height; width: 16; color: spi.window } + Rectangle { height: parent.height; width: 16; color: spd.window } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "window text" } + Rectangle { height: parent.height; width: 16; color: sp.windowText } + Rectangle { height: parent.height; width: 16; color: spi.windowText } + Rectangle { height: parent.height; width: 16; color: spd.windowText } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "button" } + Rectangle { height: parent.height; width: 16; color: sp.button } + Rectangle { height: parent.height; width: 16; color: spi.button } + Rectangle { height: parent.height; width: 16; color: spd.button } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "buttonText" } + Rectangle { height: parent.height; width: 16; color: sp.buttonText } + Rectangle { height: parent.height; width: 16; color: spi.buttonText } + Rectangle { height: parent.height; width: 16; color: spd.buttonText } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "highlight" } + Rectangle { height: parent.height; width: 16; color: sp.highlight } + Rectangle { height: parent.height; width: 16; color: spi.highlight } + Rectangle { height: parent.height; width: 16; color: spd.highlight } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "highlighted text" } + Rectangle { height: parent.height; width: 16; color: sp.highlightedText} + Rectangle { height: parent.height; width: 16; color: spi.highlightedText} + Rectangle { height: parent.height; width: 16; color: spd.highlightedText} + } + } +} diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/Root.qml new file mode 100644 index 0000000000..1b0f09558f --- /dev/null +++ b/interface/resources/qml/Root.qml @@ -0,0 +1,14 @@ +import Hifi 1.0 +import QtQuick 2.3 + +// This is our primary 'window' object to which all dialogs and controls will +// be childed. +Root { + id: root + anchors.fill: parent + + onParentChanged: { + forceActiveFocus(); + } +} + diff --git a/interface/resources/qml/RootMenu.qml b/interface/resources/qml/RootMenu.qml new file mode 100644 index 0000000000..b8c81a6589 --- /dev/null +++ b/interface/resources/qml/RootMenu.qml @@ -0,0 +1,9 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 + +Item { + Menu { + id: root + objectName: "rootMenu" + } +} diff --git a/interface/resources/qml/TestDialog.qml b/interface/resources/qml/TestDialog.qml new file mode 100644 index 0000000000..15bd790c22 --- /dev/null +++ b/interface/resources/qml/TestDialog.qml @@ -0,0 +1,94 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.3 +import "controls" + +Dialog { + title: "Test Dialog" + id: testDialog + objectName: "TestDialog" + width: 512 + height: 512 + animationDuration: 200 + + onEnabledChanged: { + if (enabled) { + edit.forceActiveFocus(); + } + } + + Item { + id: clientArea + // The client area + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + + Rectangle { + property int d: 100 + id: square + objectName: "testRect" + width: d + height: d + anchors.centerIn: parent + color: "red" + NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; } + } + + + TextEdit { + id: edit + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + clip: true + text: "test edit" + anchors.top: parent.top + anchors.topMargin: 12 + } + + Button { + x: 128 + y: 192 + text: "Test" + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + onClicked: { + console.log("Click"); + + if (square.visible) { + square.visible = false + } else { + square.visible = true + } + } + } + + Button { + id: customButton2 + y: 192 + text: "Move" + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + onClicked: { + onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0 + } + } + + Keys.onPressed: { + console.log("Key " + event.key); + switch (event.key) { + case Qt.Key_Q: + if (Qt.ControlModifier == event.modifiers) { + event.accepted = true; + break; + } + } + } + } +} diff --git a/interface/resources/qml/TestMenu.qml b/interface/resources/qml/TestMenu.qml new file mode 100644 index 0000000000..5aff18b421 --- /dev/null +++ b/interface/resources/qml/TestMenu.qml @@ -0,0 +1,115 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import Hifi 1.0 + +// Currently for testing a pure QML replacement menu +Item { + Item { + objectName: "AllActions" + Action { + id: aboutApp + objectName: "HifiAction_" + MenuConstants.AboutApp + text: qsTr("About Interface") + } + + // + // File Menu + // + Action { + id: login + objectName: "HifiAction_" + MenuConstants.Login + text: qsTr("Login") + } + Action { + id: quit + objectName: "HifiAction_" + MenuConstants.Quit + text: qsTr("Quit") + //shortcut: StandardKey.Quit + shortcut: "Ctrl+Q" + } + + + // + // Edit menu + // + Action { + id: undo + text: "Undo" + shortcut: StandardKey.Undo + } + + Action { + id: redo + text: "Redo" + shortcut: StandardKey.Redo + } + + Action { + id: animations + objectName: "HifiAction_" + MenuConstants.Animations + text: qsTr("Animations...") + } + Action { + id: attachments + text: qsTr("Attachments...") + } + Action { + id: explode + text: qsTr("Explode on quit") + checkable: true + checked: true + } + Action { + id: freeze + text: qsTr("Freeze on quit") + checkable: true + checked: false + } + ExclusiveGroup { + Action { + id: visibleToEveryone + objectName: "HifiAction_" + MenuConstants.VisibleToEveryone + text: qsTr("Everyone") + checkable: true + checked: true + } + Action { + id: visibleToFriends + objectName: "HifiAction_" + MenuConstants.VisibleToFriends + text: qsTr("Friends") + checkable: true + } + Action { + id: visibleToNoOne + objectName: "HifiAction_" + MenuConstants.VisibleToNoOne + text: qsTr("No one") + checkable: true + } + } + } + + Menu { + objectName: "rootMenu"; + Menu { + title: "File" + MenuItem { action: login } + MenuItem { action: explode } + MenuItem { action: freeze } + MenuItem { action: quit } + } + Menu { + title: "Tools" + Menu { + title: "I Am Visible To" + MenuItem { action: visibleToEveryone } + MenuItem { action: visibleToFriends } + MenuItem { action: visibleToNoOne } + } + MenuItem { action: animations } + } + Menu { + title: "Help" + MenuItem { action: aboutApp } + } + } +} diff --git a/interface/resources/qml/TestRoot.qml b/interface/resources/qml/TestRoot.qml new file mode 100644 index 0000000000..bd38c696bf --- /dev/null +++ b/interface/resources/qml/TestRoot.qml @@ -0,0 +1,43 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.3 +// Import local folder last so that our own control customizations override +// the built in ones +import "controls" + +Root { + id: root + anchors.fill: parent + onParentChanged: { + forceActiveFocus(); + } + Button { + id: messageBox + anchors.right: createDialog.left + anchors.rightMargin: 24 + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 + text: "Message" + onClicked: { + console.log("Foo") + root.information("a") + console.log("Bar") + } + } + Button { + id: createDialog + anchors.right: parent.right + anchors.rightMargin: 24 + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 + text: "Create" + onClicked: { + root.loadChild("MenuTest.qml"); + } + } + + Keys.onPressed: { + console.log("Key press root") + } +} + diff --git a/interface/resources/qml/VrMenu.qml b/interface/resources/qml/VrMenu.qml new file mode 100644 index 0000000000..d81d79aa0b --- /dev/null +++ b/interface/resources/qml/VrMenu.qml @@ -0,0 +1,326 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 +import "controls" +import "styles" + +Hifi.VrMenu { + id: root + HifiConstants { id: hifi } + + anchors.fill: parent + + objectName: "VrMenu" + + enabled: false + opacity: 0.0 + + property int animationDuration: 200 + property var models: [] + property var columns: [] + + z: 10000 + + onEnabledChanged: { + if (enabled && columns.length == 0) { + pushColumn(rootMenu.items); + } + opacity = enabled ? 1.0 : 0.0 + if (enabled) { + forceActiveFocus() + } + } + + // The actual animator + Behavior on opacity { + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutBounce + } + } + + onOpacityChanged: { + visible = (opacity != 0.0); + } + + onVisibleChanged: { + if (!visible) reset(); + } + + property var menuBuilder: Component { + Border { + HifiConstants { id: hifi } + Component.onCompleted: { + menuDepth = root.models.length - 1 + if (menuDepth == 0) { + x = lastMousePosition.x - 20 + y = lastMousePosition.y - 20 + } else { + var lastColumn = root.columns[menuDepth - 1] + x = lastColumn.x + 64; + y = lastMousePosition.y - height / 2; + } + } + border.color: hifi.colors.hifiBlue + color: hifi.colors.window + property int menuDepth + implicitHeight: listView.implicitHeight + 16 + implicitWidth: listView.implicitWidth + 16 + + Column { + id: listView + property real minWidth: 0 + anchors { + top: parent.top + topMargin: 8 + left: parent.left + leftMargin: 8 + right: parent.right + rightMargin: 8 + } + + Repeater { + model: root.models[menuDepth] + delegate: Loader { + id: loader + sourceComponent: root.itemBuilder + Binding { + target: loader.item + property: "root" + value: root + when: loader.status == Loader.Ready + } + Binding { + target: loader.item + property: "source" + value: modelData + when: loader.status == Loader.Ready + } + Binding { + target: loader.item + property: "border" + value: listView.parent + when: loader.status == Loader.Ready + } + Binding { + target: loader.item + property: "listView" + value: listView + when: loader.status == Loader.Ready + } + } + } + } + } + } + + property var itemBuilder: Component { + Item { + property var source + property var root + property var listView + property var border + implicitHeight: row.implicitHeight + 4 + implicitWidth: row.implicitWidth + label.height + // FIXME uncommenting this line results in menus that have blank spots + // rather than having the correct size + // visible: source.visible + Row { + id: row + spacing: 2 + anchors { + top: parent.top + topMargin: 2 + } + Spacer { size: 4 } + FontAwesome { + id: check + verticalAlignment: Text.AlignVCenter + y: 2 + size: label.height + text: checkText() + color: label.color + function checkText() { + if (!source || source.type != 1 || !source.checkable) { + return ""; + } + + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + if (source.exclusiveGroup) { + return source.checked ? "\uF05D" : "\uF10C" + } + return source.checked ? "\uF046" : "\uF096" + } + } + Text { + id: label + text: typedText() + color: source.enabled ? hifi.colors.text : hifi.colors.disabledText + enabled: source.enabled && source.visible + function typedText() { + if (source) { + switch(source.type) { + case 2: + return source.title; + case 1: + return source.text; + case 0: + return "-----" + } + } + return "" + } + } + } // row + + FontAwesome { + anchors { + top: row.top + } + id: tag + size: label.height + width: implicitWidth + visible: source.type == 2 + x: listView.width - width - 4 + text: "\uF0DA" + color: label.color + } + + MouseArea { + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + right: tag.right + rightMargin: -4 + } + acceptedButtons: Qt.LeftButton + hoverEnabled: true + Rectangle { + id: highlight + visible: false + anchors.fill: parent + color: "#7f0e7077" + } + Timer { + id: timer + interval: 1000 + onTriggered: parent.select(); + } + onEntered: { + /* + * Uncomment below to have menus auto-popup + * + * FIXME if we enabled timer based menu popup, either the timer has + * to be very very short or after auto popup there has to be a small + * amount of time, or a test if the mouse has moved before a click + * will be accepted, otherwise it's too easy to accidently click on + * something immediately after the auto-popup appears underneath your + * cursor + * + */ + //if (source.type == 2 && enabled) { + // timer.start() + //} + highlight.visible = source.enabled + } + onExited: { + timer.stop() + highlight.visible = false + } + onClicked: { + select(); + } + function select() { + //timer.stop(); + var popped = false; + while (columns.length - 1 > listView.parent.menuDepth) { + popColumn(); + popped = true; + } + + if (!popped || source.type != 1) { + parent.root.selectItem(parent.source); + } + } + } + } + } + + function lastColumn() { + return columns[root.columns.length - 1]; + } + + function pushColumn(items) { + models.push(items) + if (columns.length) { + var oldColumn = lastColumn(); + //oldColumn.enabled = false + } + var newColumn = menuBuilder.createObject(root); + columns.push(newColumn); + newColumn.forceActiveFocus(); + } + + function popColumn() { + if (columns.length > 0) { + var curColumn = columns.pop(); + curColumn.visible = false; + curColumn.destroy(); + models.pop(); + } + + if (columns.length == 0) { + enabled = false; + return; + } + + curColumn = lastColumn(); + curColumn.enabled = true; + curColumn.opacity = 1.0; + curColumn.forceActiveFocus(); + } + + function selectItem(source) { + switch (source.type) { + case 2: + pushColumn(source.items) + break; + case 1: + source.trigger() + enabled = false + break; + case 0: + break; + } + } + + function reset() { + while (columns.length > 0) { + popColumn(); + } + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Escape: + root.popColumn() + event.accepted = true; + } + } + + MouseArea { + anchors.fill: parent + id: mouseArea + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button == Qt.RightButton) { + root.popColumn(); + } else { + root.enabled = false; + } + } + } +} diff --git a/interface/resources/qml/controls/Button.qml b/interface/resources/qml/controls/Button.qml new file mode 100644 index 0000000000..989d5b579c --- /dev/null +++ b/interface/resources/qml/controls/Button.qml @@ -0,0 +1,9 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 as Original +import QtQuick.Controls.Styles 1.3 +import "." +import "../styles" + +Original.Button { + style: ButtonStyle { } +} diff --git a/interface/resources/qml/controls/CheckBox.qml b/interface/resources/qml/controls/CheckBox.qml new file mode 100644 index 0000000000..fe836a0e89 --- /dev/null +++ b/interface/resources/qml/controls/CheckBox.qml @@ -0,0 +1,16 @@ +import QtQuick.Controls 1.3 as Original +import QtQuick.Controls.Styles 1.3 +import "../styles" +import "." +Original.CheckBox { + text: "Check Box" + style: CheckBoxStyle { + indicator: FontAwesome { + text: control.checked ? "\uf046" : "\uf096" + } + label: Text { + text: control.text + } + } + +} \ No newline at end of file diff --git a/interface/resources/qml/controls/Dialog.qml b/interface/resources/qml/controls/Dialog.qml new file mode 100644 index 0000000000..f32b4e2e66 --- /dev/null +++ b/interface/resources/qml/controls/Dialog.qml @@ -0,0 +1,158 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import "." +import "../styles" + +/* + * FIXME Need to create a client property here so that objects can be + * placed in it without having to think about positioning within the outer + * window. + * + * Examine the QML ApplicationWindow.qml source for how it does this + * + */ +DialogBase { + id: root + HifiConstants { id: hifi } + // FIXME better placement via a window manager + x: parent ? parent.width / 2 - width / 2 : 0 + y: parent ? parent.height / 2 - height / 2 : 0 + + property bool destroyOnInvisible: false + property bool destroyOnCloseButton: true + property bool resizable: false + + property int animationDuration: hifi.effects.fadeInDuration + property int minX: 256 + property int minY: 256 + readonly property int topMargin: root.height - clientBorder.height + hifi.layout.spacing + + /* + * Support for animating the dialog in and out. + */ + enabled: false + scale: 0.0 + + // The offscreen UI will enable an object, rather than manipulating it's + // visibility, so that we can do animations in both directions. Because + // visibility and enabled are boolean flags, they cannot be animated. So when + // enabled is change, we modify a property that can be animated, like scale or + // opacity, and then when the target animation value is reached, we can + // modify the visibility + onEnabledChanged: { + scale = enabled ? 1.0 : 0.0 + } + + // The actual animator + Behavior on scale { + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutBounce + } + } + + // Once we're scaled to 0, disable the dialog's visibility + onScaleChanged: { + visible = (scale != 0.0); + } + + // Some dialogs should be destroyed when they become invisible, + // so handle that + onVisibleChanged: { + if (!visible && destroyOnInvisible) { + destroy(); + } + } + + // our close function performs the same way as the OffscreenUI class: + // don't do anything but manipulate the enabled flag and let the other + // mechanisms decide if the window should be destroyed after the close + // animation completes + function close() { + if (destroyOnCloseButton) { + destroyOnInvisible = true + } + enabled = false; + } + + /* + * Resize support + */ + function deltaSize(dx, dy) { + width = Math.max(width + dx, minX) + height = Math.max(height + dy, minY) + } + + MouseArea { + id: sizeDrag + enabled: root.resizable + property int startX + property int startY + anchors.right: parent.right + anchors.bottom: parent.bottom + width: 16 + height: 16 + z: 1000 + hoverEnabled: true + onPressed: { + startX = mouseX + startY = mouseY + } + onPositionChanged: { + if (pressed) { + root.deltaSize((mouseX - startX), (mouseY - startY)) + startX = mouseX + startY = mouseY + } + } + } + + MouseArea { + id: titleDrag + x: root.titleX + y: root.titleY + width: root.titleWidth + height: root.titleHeight + + drag { + target: root + minimumX: 0 + minimumY: 0 + maximumX: root.parent ? root.parent.width - root.width : 0 + maximumY: root.parent ? root.parent.height - root.height : 0 + } + + Row { + id: windowControls + anchors.bottom: parent.bottom + anchors.top: parent.top + anchors.right: parent.right + anchors.rightMargin: hifi.layout.spacing + FontAwesome { + id: icon + anchors.verticalCenter: parent.verticalCenter + size: root.titleHeight - hifi.layout.spacing * 2 + color: "red" + text: "\uf00d" + MouseArea { + anchors.margins: hifi.layout.spacing / 2 + anchors.fill: parent + onClicked: { + root.close(); + } + } + } + } + } + + Keys.onPressed: { + switch(event.key) { + case Qt.Key_W: + if (event.modifiers == Qt.ControlModifier) { + event.accepted = true + enabled = false + } + break; + } + } +} diff --git a/interface/resources/qml/controls/DialogBase.qml b/interface/resources/qml/controls/DialogBase.qml new file mode 100644 index 0000000000..a6616fc731 --- /dev/null +++ b/interface/resources/qml/controls/DialogBase.qml @@ -0,0 +1,98 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import "." +import "../styles" + +Item { + id: root + HifiConstants { id: hifi } + implicitHeight: contentImplicitHeight + titleBorder.height + hifi.styles.borderWidth + implicitWidth: contentImplicitWidth + hifi.styles.borderWidth * 2 + property string title + property int titleSize: titleBorder.height + 12 + property string frameColor: hifi.colors.hifiBlue + property string backgroundColor: hifi.colors.dialogBackground + property bool active: false + property real contentImplicitWidth: 800 + property real contentImplicitHeight: 800 + + property alias titleBorder: titleBorder + readonly property alias titleX: titleBorder.x + readonly property alias titleY: titleBorder.y + readonly property alias titleWidth: titleBorder.width + readonly property alias titleHeight: titleBorder.height + + // readonly property real borderWidth: hifi.styles.borderWidth + readonly property real borderWidth: 0 + property alias clientBorder: clientBorder + readonly property real clientX: clientBorder.x + borderWidth + readonly property real clientY: clientBorder.y + borderWidth + readonly property real clientWidth: clientBorder.width - borderWidth * 2 + readonly property real clientHeight: clientBorder.height - borderWidth * 2 + + /* + * Window decorations, with a title bar and frames + */ + Border { + id: windowBorder + anchors.fill: parent + border.color: root.frameColor + border.width: 0 + color: "#00000000" + + Border { + id: titleBorder + height: hifi.layout.windowTitleHeight + anchors.right: parent.right + anchors.left: parent.left + border.color: root.frameColor + border.width: 0 + clip: true + color: root.active ? + hifi.colors.activeWindow.headerBackground : + hifi.colors.inactiveWindow.headerBackground + + + Text { + id: titleText + color: root.active ? + hifi.colors.activeWindow.headerText : + hifi.colors.inactiveWindow.headerText + text: root.title + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + } + } // header border + + // These two rectangles hide the curves between the title area + // and the client area + Rectangle { + y: titleBorder.height - titleBorder.radius + height: titleBorder.radius + width: titleBorder.width + color: titleBorder.color + visible: borderWidth == 0 + } + + Rectangle { + y: titleBorder.height + width: clientBorder.width + height: clientBorder.radius + color: clientBorder.color + } + + Border { + id: clientBorder + border.width: 0 + border.color: root.frameColor + color: root.backgroundColor + anchors.bottom: parent.bottom + anchors.top: titleBorder.bottom + anchors.topMargin: -titleBorder.border.width + anchors.right: parent.right + anchors.left: parent.left + } // client border + } // window border + +} diff --git a/interface/resources/qml/controls/FontAwesome.qml b/interface/resources/qml/controls/FontAwesome.qml new file mode 100644 index 0000000000..50d7e96fb5 --- /dev/null +++ b/interface/resources/qml/controls/FontAwesome.qml @@ -0,0 +1,16 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 + +Text { + id: root + FontLoader { id: iconFont; source: "../../fonts/fontawesome-webfont.ttf"; } + property int size: 32 + width: size + height: size + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: iconFont.name +} + diff --git a/interface/resources/qml/controls/README.md b/interface/resources/qml/controls/README.md new file mode 100644 index 0000000000..7f05f32a63 --- /dev/null +++ b/interface/resources/qml/controls/README.md @@ -0,0 +1,2 @@ +These are our own custom controls with the same names as existing controls, but +customized for readability / usability in VR. diff --git a/interface/resources/qml/controls/Slider.qml b/interface/resources/qml/controls/Slider.qml new file mode 100644 index 0000000000..ace20f1e68 --- /dev/null +++ b/interface/resources/qml/controls/Slider.qml @@ -0,0 +1,8 @@ +import QtQuick.Controls 1.3 as Original +import QtQuick.Controls.Styles 1.3 + +import "../styles" +import "." + +Original.Slider { +} diff --git a/interface/resources/qml/controls/Spacer.qml b/interface/resources/qml/controls/Spacer.qml new file mode 100644 index 0000000000..3c4f7456d6 --- /dev/null +++ b/interface/resources/qml/controls/Spacer.qml @@ -0,0 +1,11 @@ +import QtQuick 2.4 +import "../styles" + +Item { + id: root + HifiConstants { id: hifi } + property real size: hifi.layout.spacing + property real multiplier: 1.0 + height: size * multiplier + width: size * multiplier +} diff --git a/interface/resources/qml/controls/SpinBox.qml b/interface/resources/qml/controls/SpinBox.qml new file mode 100644 index 0000000000..7e44b9e4a3 --- /dev/null +++ b/interface/resources/qml/controls/SpinBox.qml @@ -0,0 +1,15 @@ + +import QtQuick.Controls 1.3 as Original +import QtQuick.Controls.Styles 1.3 + +import "../styles" +import "." + +Original.SpinBox { + style: SpinBoxStyle { + HifiConstants { id: hifi } + font.family: hifi.fonts.fontFamily + font.pointSize: hifi.fonts.fontSize + } + +} diff --git a/interface/resources/qml/controls/Text.qml b/interface/resources/qml/controls/Text.qml new file mode 100644 index 0000000000..4f82f2d9e4 --- /dev/null +++ b/interface/resources/qml/controls/Text.qml @@ -0,0 +1,9 @@ +import QtQuick 2.3 as Original +import "../styles" + +Original.Text { + HifiConstants { id: hifi } + font.family: hifi.fonts.fontFamily + font.pointSize: hifi.fonts.fontSize +} + diff --git a/interface/resources/qml/controls/TextAndSlider.qml b/interface/resources/qml/controls/TextAndSlider.qml new file mode 100644 index 0000000000..302c096878 --- /dev/null +++ b/interface/resources/qml/controls/TextAndSlider.qml @@ -0,0 +1,24 @@ +import QtQuick 2.3 as Original +import "../styles" +import "." + +Original.Item { + property alias text: label.text + property alias value: slider.value + + Text { + id: label + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + verticalAlignment: Original.Text.AlignVCenter + } + + Slider { + id: slider + width: 120 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } +} diff --git a/interface/resources/qml/controls/TextAndSpinBox.qml b/interface/resources/qml/controls/TextAndSpinBox.qml new file mode 100644 index 0000000000..a32a36a1f8 --- /dev/null +++ b/interface/resources/qml/controls/TextAndSpinBox.qml @@ -0,0 +1,26 @@ +import QtQuick 2.3 as Original +import "../styles" +import "." + +Original.Item { + property alias text: label.text + property alias value: spinBox.value + + Text { + id: label + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + verticalAlignment: Original.Text.AlignVCenter + text: "Minimum HMD FPS" + } + SpinBox { + id: spinBox + width: 120 + maximumValue: 240 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + +} diff --git a/interface/resources/qml/controls/TextArea.qml b/interface/resources/qml/controls/TextArea.qml new file mode 100644 index 0000000000..a86e76620a --- /dev/null +++ b/interface/resources/qml/controls/TextArea.qml @@ -0,0 +1,9 @@ +import QtQuick.Controls 2.3 as Original +import "../styles" + +Original.TextArea { + HifiConstants { id: hifi } + font.family: hifi.fonts.fontFamily + font.pointSize: hifi.fonts.fontSize +} + diff --git a/interface/resources/qml/controls/TextEdit.qml b/interface/resources/qml/controls/TextEdit.qml new file mode 100644 index 0000000000..b59b20a3d6 --- /dev/null +++ b/interface/resources/qml/controls/TextEdit.qml @@ -0,0 +1,9 @@ +import QtQuick 2.3 as Original +import "../styles" + +Original.TextEdit { + HifiConstants { id: hifi } + font.family: hifi.fonts.fontFamily + font.pointSize: hifi.fonts.fontSize +} + diff --git a/interface/resources/qml/controls/TextHeader.qml b/interface/resources/qml/controls/TextHeader.qml new file mode 100644 index 0000000000..9ce1da4ac2 --- /dev/null +++ b/interface/resources/qml/controls/TextHeader.qml @@ -0,0 +1,9 @@ +import "." +import "../styles" + +Text { + HifiConstants { id: hifi } + color: hifi.colors.hifiBlue + font.pointSize: hifi.fonts.headerPointSize + font.bold: true +} diff --git a/interface/resources/qml/controls/TextInput.qml b/interface/resources/qml/controls/TextInput.qml new file mode 100644 index 0000000000..d533c67bd6 --- /dev/null +++ b/interface/resources/qml/controls/TextInput.qml @@ -0,0 +1,36 @@ +import QtQuick 2.3 as Original +import "../styles" +import "." + +Original.TextInput { + id: root + HifiConstants { id: hifi } + property string helperText + height: hifi.layout.rowHeight + clip: true + color: hifi.colors.text + verticalAlignment: Original.TextInput.AlignVCenter + font.family: hifi.fonts.fontFamily + font.pointSize: hifi.fonts.fontSize + +/* + Original.Rectangle { + // Render the rectangle as background + z: -1 + anchors.fill: parent + color: hifi.colors.inputBackground + } +*/ + Text { + anchors.fill: parent + font.pointSize: parent.font.pointSize + font.family: parent.font.family + verticalAlignment: parent.verticalAlignment + horizontalAlignment: parent.horizontalAlignment + text: root.helperText + color: hifi.colors.hintText + visible: !root.text + } +} + + diff --git a/interface/resources/qml/controls/TextInputAndButton.qml b/interface/resources/qml/controls/TextInputAndButton.qml new file mode 100644 index 0000000000..60e9001d72 --- /dev/null +++ b/interface/resources/qml/controls/TextInputAndButton.qml @@ -0,0 +1,40 @@ +import QtQuick 2.3 as Original +import "../styles" +import "." + +Original.Item { + id: root + HifiConstants { id: hifi } + height: hifi.layout.rowHeight + property string text + property string helperText + property string buttonText + property int buttonWidth: 0 + property alias input: input + property alias button: button + signal clicked() + + TextInput { + id: input + text: root.text + helperText: root.helperText + anchors.left: parent.left + anchors.right: button.left + anchors.rightMargin: 8 + anchors.bottom: parent.bottom + anchors.top: parent.top + } + + Button { + id: button + clip: true + width: root.buttonWidth ? root.buttonWidth : implicitWidth + text: root.buttonText + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: parent.top + onClicked: root.clicked() + } +} + + diff --git a/interface/resources/qml/styles/Border.qml b/interface/resources/qml/styles/Border.qml new file mode 100644 index 0000000000..ae6bce9e39 --- /dev/null +++ b/interface/resources/qml/styles/Border.qml @@ -0,0 +1,12 @@ +import QtQuick 2.3 + +Rectangle { + HifiConstants { id: hifi } + implicitHeight: 64 + implicitWidth: 64 + color: hifi.colors.window + border.color: hifi.colors.hifiBlue + border.width: hifi.styles.borderWidth + radius: hifi.styles.borderRadius +} + diff --git a/interface/resources/qml/styles/ButtonStyle.qml b/interface/resources/qml/styles/ButtonStyle.qml new file mode 100644 index 0000000000..bcb167f4dc --- /dev/null +++ b/interface/resources/qml/styles/ButtonStyle.qml @@ -0,0 +1,23 @@ +import QtQuick 2.4 as Original +import QtQuick.Controls.Styles 1.3 as OriginalStyles +import "." +import "../controls" + +OriginalStyles.ButtonStyle { + HifiConstants { id: hifi } + padding { + top: 8 + left: 12 + right: 12 + bottom: 8 + } + background: Border { + anchors.fill: parent + } + label: Text { + verticalAlignment: Original.Text.AlignVCenter + horizontalAlignment: Original.Text.AlignHCenter + text: control.text + color: control.enabled ? hifi.colors.text : hifi.colors.disabledText + } +} diff --git a/interface/resources/qml/styles/HifiConstants.qml b/interface/resources/qml/styles/HifiConstants.qml new file mode 100644 index 0000000000..d24e9ca9be --- /dev/null +++ b/interface/resources/qml/styles/HifiConstants.qml @@ -0,0 +1,61 @@ +import QtQuick 2.4 + +Item { + SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active } + readonly property alias colors: colors + readonly property alias layout: layout + readonly property alias fonts: fonts + readonly property alias styles: styles + readonly property alias effects: effects + + Item { + id: colors + readonly property color hifiBlue: "#0e7077" + readonly property color window: sysPalette.window + readonly property color dialogBackground: sysPalette.window + //readonly property color dialogBackground: "#00000000" + readonly property color inputBackground: "white" + readonly property color background: sysPalette.dark + readonly property color text: sysPalette.text + readonly property color disabledText: "gray" + readonly property color hintText: sysPalette.dark + readonly property color light: sysPalette.light + readonly property alias activeWindow: activeWindow + readonly property alias inactiveWindow: inactiveWindow + QtObject { + id: activeWindow + readonly property color headerBackground: "white" + readonly property color headerText: "black" + } + QtObject { + id: inactiveWindow + readonly property color headerBackground: "gray" + readonly property color headerText: "black" + } + } + + QtObject { + id: fonts + readonly property real headerPointSize: 24 + readonly property string fontFamily: "Helvetica" + readonly property real fontSize: 18 + } + + QtObject { + id: layout + property int spacing: 8 + property int rowHeight: 40 + property int windowTitleHeight: 48 + } + + QtObject { + id: styles + readonly property int borderWidth: 5 + readonly property int borderRadius: borderWidth * 2 + } + + QtObject { + id: effects + readonly property int fadeInDuration: 400 + } +} diff --git a/interface/resources/qml/styles/HifiPalette.qml b/interface/resources/qml/styles/HifiPalette.qml new file mode 100644 index 0000000000..421fa2c75d --- /dev/null +++ b/interface/resources/qml/styles/HifiPalette.qml @@ -0,0 +1,11 @@ +import QtQuick 2.4 + +Item { + property string hifiBlue: "#0e7077" + property alias colors: colorsObj + + Item { + id: colorsObj + property string hifiRed: "red" + } +} diff --git a/interface/resources/qml/styles/IconButtonStyle.qml b/interface/resources/qml/styles/IconButtonStyle.qml new file mode 100644 index 0000000000..812cd493b0 --- /dev/null +++ b/interface/resources/qml/styles/IconButtonStyle.qml @@ -0,0 +1,10 @@ +ButtonStyle { + background: Item { anchors.fill: parent } + label: FontAwesome { + id: icon + font.pointSize: 18 + property alias unicode: text + text: control.text + color: control.enabled ? hifi.colors.text : hifi.colors.disabledText + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 98ef907764..bfda9629a0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -64,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +83,7 @@ #include #include #include +#include #include @@ -109,7 +110,6 @@ #include "devices/MIDIManager.h" #include "devices/OculusManager.h" #include "devices/TV3DManager.h" -#include "devices/Visage.h" #include "gpu/Batch.h" #include "gpu/GLBackend.h" @@ -137,6 +137,7 @@ #include "ui/Snapshot.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" +#include "ui/AddressBarDialog.h" // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU #if defined(Q_OS_WIN) @@ -145,6 +146,23 @@ extern "C" { } #endif +enum CustomEventTypes { + Lambda = QEvent::User + 1 +}; + +class LambdaEvent : public QEvent { + std::function _fun; +public: + LambdaEvent(const std::function & fun) : + QEvent(static_cast(Lambda)), _fun(fun) { + } + LambdaEvent(std::function && fun) : + QEvent(static_cast(Lambda)), _fun(fun) { + } + void call() { _fun(); } +}; + + using namespace std; // Starfield information @@ -165,8 +183,6 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D const QString DEFAULT_SCRIPTS_JS_URL = "http://s3.amazonaws.com/hifi-public/scripts/defaultScripts.js"; -bool renderCollisionHulls = false; - #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { public: @@ -209,8 +225,12 @@ public: void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); - + if (!logMessage.isEmpty()) { +#ifdef Q_OS_WIN + OutputDebugStringA(logMessage.toLocal8Bit().constData()); + OutputDebugStringA("\n"); +#endif Application::getInstance()->getLogger()->addMessage(qPrintable(logMessage + "\n")); } } @@ -243,7 +263,6 @@ bool setupEssentials(int& argc, char** argv) { auto ambientOcclusionEffect = DependencyManager::set(); auto textureCache = DependencyManager::set(); auto animationCache = DependencyManager::set(); - auto visage = DependencyManager::set(); auto ddeFaceTracker = DependencyManager::set(); auto modelBlender = DependencyManager::set(); auto audioToolBox = DependencyManager::set(); @@ -260,6 +279,7 @@ bool setupEssentials(int& argc, char** argv) { #endif auto discoverabilityManager = DependencyManager::set(); auto sceneScriptingInterface = DependencyManager::set(); + auto offscreenUi = DependencyManager::set(); return true; } @@ -316,8 +336,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif + _logger = new FileLogger(this); // After setting organization name in order to get correct directory + qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); @@ -335,9 +357,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _bookmarks = new Bookmarks(); // Before setting up the menu - // call Menu getInstance static method to set up the menu - _window->setMenuBar(Menu::getInstance()); - _runningScriptsWidget = new RunningScriptsWidget(_window); // start the nodeThread so its event loop is running @@ -444,6 +463,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : addressManager->setOrientationGetter(getOrientationForPath); connect(addressManager.data(), &AddressManager::rootPlaceNameChanged, this, &Application::updateWindowTitle); + connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); #ifdef _WIN32 WSADATA WsaData; @@ -562,8 +582,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : #endif this->installEventFilter(this); + // The offscreen UI needs to intercept the mouse and keyboard + // events coming from the onscreen window + _glWidget->installEventFilter(DependencyManager::get().data()); } + void Application::aboutToQuit() { emit beforeAboutToQuit(); @@ -612,6 +636,10 @@ void Application::cleanupBeforeQuit() { // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy(); + +#ifdef HAVE_DDE + DependencyManager::destroy(); +#endif } Application::~Application() { @@ -633,6 +661,7 @@ Application::~Application() { // stop the glWidget frame timer so it doesn't call paintGL _glWidget->stopFrameTimer(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); @@ -693,6 +722,17 @@ void Application::initializeGL() { initDisplay(); qCDebug(interfaceapp, "Initialized Display."); + // The UI can't be created until the primary OpenGL + // context is created, because it needs to share + // texture resources + initializeUi(); + qCDebug(interfaceapp, "Initialized Offscreen UI."); + _glWidget->makeCurrent(); + + // call Menu getInstance static method to set up the menu + // Needs to happen AFTER the QML UI initialization + _window->setMenuBar(Menu::getInstance()); + init(); qCDebug(interfaceapp, "init() complete."); @@ -720,10 +760,45 @@ void Application::initializeGL() { // update before the first render update(1.0f / _fps); - + InfoView::showFirstTime(INFO_HELP_PATH); } +void Application::initializeUi() { + AddressBarDialog::registerType(); + LoginDialog::registerType(); + MessageDialog::registerType(); + VrMenu::registerType(); + + auto offscreenUi = DependencyManager::get(); + offscreenUi->create(_glWidget->context()->contextHandle()); + offscreenUi->resize(_glWidget->size()); + offscreenUi->setProxyWindow(_window->windowHandle()); + offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); + offscreenUi->load("Root.qml"); + offscreenUi->load("RootMenu.qml"); + VrMenu::load(); + VrMenu::executeQueuedLambdas(); + offscreenUi->setMouseTranslator([this](const QPointF& p){ + if (OculusManager::isConnected()) { + glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p)); + return QPointF(pos.x, pos.y); + } + return QPointF(p); + }); + offscreenUi->resume(); + connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect & r){ + static qreal oldDevicePixelRatio = 0; + qreal devicePixelRatio = _glWidget->devicePixelRatio(); + if (devicePixelRatio != oldDevicePixelRatio) { + oldDevicePixelRatio = devicePixelRatio; + qDebug() << "Device pixel ratio changed, triggering GL resize"; + resizeGL(_glWidget->width(), + _glWidget->height()); + } + }); +} + void Application::paintGL() { PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("paintGL"); @@ -798,7 +873,7 @@ void Application::paintGL() { DependencyManager::get()->prepare(); // Viewport is assigned to the size of the framebuffer - QSize size = DependencyManager::get()->getPrimaryFramebufferObject()->size(); + QSize size = DependencyManager::get()->getFrameBufferSize(); glViewport(0, 0, size.width(), size.height()); glMatrixMode(GL_MODELVIEW); @@ -818,7 +893,7 @@ void Application::paintGL() { { PerformanceTimer perfTimer("renderOverlay"); - _applicationOverlay.renderOverlay(true); + _applicationOverlay.renderOverlay(); _applicationOverlay.displayOverlayTexture(); } } @@ -863,6 +938,9 @@ void Application::resizeGL(int width, int height) { updateProjectionMatrix(); glLoadIdentity(); + auto offscreenUi = DependencyManager::get(); + offscreenUi->resize(_glWidget->size()); + // update Stats width // let's set horizontal offset to give stats some margin to mirror int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; @@ -911,6 +989,48 @@ bool Application::importSVOFromURL(const QString& urlString) { } bool Application::event(QEvent* event) { + switch (event->type()) { + case Lambda: + ((LambdaEvent*)event)->call(); + return true; + + case QEvent::MouseMove: + mouseMoveEvent((QMouseEvent*)event); + return true; + case QEvent::MouseButtonPress: + mousePressEvent((QMouseEvent*)event); + return true; + case QEvent::MouseButtonRelease: + mouseReleaseEvent((QMouseEvent*)event); + return true; + case QEvent::KeyPress: + keyPressEvent((QKeyEvent*)event); + return true; + case QEvent::KeyRelease: + keyReleaseEvent((QKeyEvent*)event); + return true; + case QEvent::FocusOut: + focusOutEvent((QFocusEvent*)event); + return true; + case QEvent::TouchBegin: + touchBeginEvent(static_cast(event)); + event->accept(); + return true; + case QEvent::TouchEnd: + touchEndEvent(static_cast(event)); + return true; + case QEvent::TouchUpdate: + touchUpdateEvent(static_cast(event)); + return true; + case QEvent::Wheel: + wheelEvent(static_cast(event)); + return true; + case QEvent::Drop: + dropEvent(static_cast(event)); + return true; + default: + break; + } // handle custom URL if (event->type() == QEvent::FileOpen) { @@ -931,13 +1051,18 @@ bool Application::event(QEvent* event) { if (HFActionEvent::types().contains(event->type())) { _controllerScriptingInterface.handleMetaEvent(static_cast(event)); } - + return QApplication::event(event); } bool Application::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::ShortcutOverride) { + if (DependencyManager::get()->shouldSwallowShortcut(event)) { + event->accept(); + return true; + } + // Filter out captured keys before they're used for shortcut actions. if (_controllerScriptingInterface.isKeyCaptured(static_cast(event))) { event->accept(); @@ -948,8 +1073,10 @@ bool Application::eventFilter(QObject* object, QEvent* event) { return false; } -void Application::keyPressEvent(QKeyEvent* event) { +static bool _altPressed{ false }; +void Application::keyPressEvent(QKeyEvent* event) { + _altPressed = event->key() == Qt::Key_Alt; _keysPressed.insert(event->key()); _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts @@ -965,12 +1092,19 @@ void Application::keyPressEvent(QKeyEvent* event) { bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { break; + case Qt::Key_Enter: + case Qt::Key_Return: + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + break; + case Qt::Key_L: - if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::LodTools); - } else if (isMeta) { + if (isShifted && isMeta) { Menu::getInstance()->triggerOption(MenuOption::Log); - } + } else if (isMeta) { + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + } else if (isShifted) { + Menu::getInstance()->triggerOption(MenuOption::LodTools); + } break; case Qt::Key_E: @@ -1019,7 +1153,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_A: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Atmosphere); - } else { + } else if (!isMeta) { _myAvatar->setDriveKeys(ROT_LEFT, 1.0f); } break; @@ -1030,11 +1164,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_Return: - case Qt::Key_Enter: - Menu::getInstance()->triggerOption(MenuOption::AddressBar); - break; - case Qt::Key_Backslash: Menu::getInstance()->triggerOption(MenuOption::Chat); break; @@ -1188,11 +1317,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; } - case Qt::Key_Comma: { - renderCollisionHulls = !renderCollisionHulls; - break; - } - default: event->ignore(); break; @@ -1200,7 +1324,19 @@ void Application::keyPressEvent(QKeyEvent* event) { } } + +//#define VR_MENU_ONLY_IN_HMD + void Application::keyReleaseEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Alt && _altPressed && _window->isActiveWindow()) { +#ifdef VR_MENU_ONLY_IN_HMD + if (OculusManager::isConnected()) { +#endif + VrMenu::toggle(); +#ifdef VR_MENU_ONLY_IN_HMD + } +#endif + } _keysPressed.remove(event->key()); @@ -1211,6 +1347,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { return; } + switch (event->key()) { case Qt::Key_E: case Qt::Key_PageUp: @@ -1330,6 +1467,9 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { } void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { + // Inhibit the menu if the user is using alt-mouse dragging + _altPressed = false; + if (!_aboutToQuit) { _entities.mousePressEvent(event, deviceID); } @@ -1404,9 +1544,12 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { } void Application::touchUpdateEvent(QTouchEvent* event) { - TouchEvent thisEvent(*event, _lastTouchEvent); - _controllerScriptingInterface.emitTouchUpdateEvent(thisEvent); // send events to any registered scripts - _lastTouchEvent = thisEvent; + _altPressed = false; + if (event->type() == QEvent::TouchUpdate) { + TouchEvent thisEvent(*event, _lastTouchEvent); + _controllerScriptingInterface.emitTouchUpdateEvent(thisEvent); // send events to any registered scripts + _lastTouchEvent = thisEvent; + } // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isTouchCaptured()) { @@ -1437,6 +1580,7 @@ void Application::touchUpdateEvent(QTouchEvent* event) { } void Application::touchBeginEvent(QTouchEvent* event) { + _altPressed = false; TouchEvent thisEvent(*event); // on touch begin, we don't compare to last event _controllerScriptingInterface.emitTouchBeginEvent(thisEvent); // send events to any registered scripts @@ -1451,6 +1595,7 @@ void Application::touchBeginEvent(QTouchEvent* event) { } void Application::touchEndEvent(QTouchEvent* event) { + _altPressed = false; TouchEvent thisEvent(*event, _lastTouchEvent); _controllerScriptingInterface.emitTouchEndEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; @@ -1467,7 +1612,7 @@ void Application::touchEndEvent(QTouchEvent* event) { } void Application::wheelEvent(QWheelEvent* event) { - + _altPressed = false; _controllerScriptingInterface.emitWheelEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -1494,6 +1639,17 @@ void Application::dropEvent(QDropEvent *event) { } } +void Application::dragEnterEvent(QDragEnterEvent* event) { + const QMimeData* mimeData = event->mimeData(); + foreach(QUrl url, mimeData->urls()) { + auto urlString = url.toString(); + if (canAcceptURL(urlString)) { + event->acceptProposedAction(); + break; + } + } +} + bool Application::acceptSnapshot(const QString& urlString) { QUrl url(urlString); QString snapshotPath = url.toLocalFile(); @@ -1732,21 +1888,21 @@ int Application::getMouseDragStartedY() const { FaceTracker* Application::getActiveFaceTracker() { auto faceshift = DependencyManager::get(); - auto visage = DependencyManager::get(); auto dde = DependencyManager::get(); return (dde->isActive() ? static_cast(dde.data()) : - (faceshift->isActive() ? static_cast(faceshift.data()) : - (visage->isActive() ? static_cast(visage.data()) : NULL))); + (faceshift->isActive() ? static_cast(faceshift.data()) : NULL)); } void Application::setActiveFaceTracker() { #ifdef HAVE_FACESHIFT DependencyManager::get()->setTCPEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)); -#endif - DependencyManager::get()->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::DDEFaceRegression)); -#ifdef HAVE_VISAGE - DependencyManager::get()->updateEnabled(); +#endif +#ifdef HAVE_DDE + bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera); + Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE); + Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE); + DependencyManager::get()->setEnabled(isUsingDDE); #endif } @@ -1921,7 +2077,6 @@ void Application::init() { // initialize our face trackers after loading the menu settings DependencyManager::get()->init(); DependencyManager::get()->init(); - DependencyManager::get()->init(); Leapmotion::init(); RealSense::init(); @@ -2226,6 +2381,7 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("physics"); _myAvatar->relayDriveKeysToCharacterController(); _physicsEngine.stepSimulation(); + _physicsEngine.dumpStatsIfNecessary(); } if (!_aboutToQuit) { @@ -2636,8 +2792,9 @@ void Application::updateShadowMap() { activeRenderingThread = QThread::currentThread(); PerformanceTimer perfTimer("shadowMap"); - QOpenGLFramebufferObject* fbo = DependencyManager::get()->getShadowFramebufferObject(); - fbo->bind(); + auto shadowFramebuffer = DependencyManager::get()->getShadowFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(shadowFramebuffer)); + glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); @@ -2653,16 +2810,18 @@ void Application::updateShadowMap() { loadViewFrustum(_myCamera, _viewFrustum); int matrixCount = 1; - int targetSize = fbo->width(); + //int targetSize = fbo->width(); + int sourceSize = shadowFramebuffer->getWidth(); + int targetSize = shadowFramebuffer->getWidth(); float targetScale = 1.0f; if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { matrixCount = CASCADED_SHADOW_MATRIX_COUNT; - targetSize = fbo->width() / 2; + targetSize = sourceSize / 2; targetScale = 0.5f; } for (int i = 0; i < matrixCount; i++) { const glm::vec2& coord = MAP_COORDS[i]; - glViewport(coord.s * fbo->width(), coord.t * fbo->height(), targetSize, targetSize); + glViewport(coord.s * sourceSize, coord.t * sourceSize, targetSize, targetSize); // if simple shadow then since the resolution is twice as much as with cascaded, cover 2 regions with the map, not just one int regionIncrement = (matrixCount == 1 ? 2 : 1); @@ -2785,7 +2944,7 @@ void Application::updateShadowMap() { glMatrixMode(GL_MODELVIEW); } - fbo->release(); + // fbo->release(); glViewport(0, 0, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); activeRenderingThread = nullptr; @@ -2831,7 +2990,8 @@ PickRay Application::computePickRay(float x, float y) { } QImage Application::renderAvatarBillboard() { - DependencyManager::get()->getPrimaryFramebufferObject()->bind(); + auto primaryFramebuffer = DependencyManager::get()->getPrimaryFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFramebuffer)); // the "glow" here causes an alpha of one Glower glower; @@ -2844,7 +3004,7 @@ QImage Application::renderAvatarBillboard() { QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); - DependencyManager::get()->getPrimaryFramebufferObject()->release(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); return image; } @@ -3001,13 +3161,21 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs PerformanceTimer perfTimer("entities"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... entities..."); - if (renderCollisionHulls) { - _entities.render(RenderArgs::DEBUG_RENDER_MODE, renderSide); - } else if (theCamera.getMode() == CAMERA_MODE_MIRROR) { - _entities.render(RenderArgs::MIRROR_RENDER_MODE, renderSide); - } else { - _entities.render(RenderArgs::DEFAULT_RENDER_MODE, renderSide); + + RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE; + RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE; + + if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { + renderDebugFlags = (RenderArgs::DebugFlags) (renderDebugFlags | (int) RenderArgs::RENDER_DEBUG_HULLS); } + if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) { + renderDebugFlags = + (RenderArgs::DebugFlags) (renderDebugFlags | (int) RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP); + } + if (theCamera.getMode() == CAMERA_MODE_MIRROR) { + renderMode = RenderArgs::MIRROR_RENDER_MODE; + } + _entities.render(renderMode, renderSide, renderDebugFlags); } // render JS/scriptable overlays @@ -3036,7 +3204,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs { DependencyManager::get()->setAmbientLightMode(getRenderAmbientLight()); auto skyStage = DependencyManager::get()->getSkyStage(); - DependencyManager::get()->setGlobalLight(skyStage->getSunLight()->getDirection(), skyStage->getSunLight()->getColor(), skyStage->getSunLight()->getIntensity()); + DependencyManager::get()->setGlobalLight(skyStage->getSunLight()->getDirection(), skyStage->getSunLight()->getColor(), skyStage->getSunLight()->getIntensity(), skyStage->getSunLight()->getAmbientIntensity()); DependencyManager::get()->setGlobalAtmosphere(skyStage->getAtmosphere()); PROFILE_RANGE("DeferredLighting"); @@ -3251,7 +3419,6 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { void Application::resetSensors() { DependencyManager::get()->reset(); - DependencyManager::get()->reset(); DependencyManager::get()->reset(); OculusManager::reset(); @@ -3617,6 +3784,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("ScriptDiscoveryService", this->getRunningScriptsWidget()); + #ifdef HAVE_RTMIDI scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); #endif @@ -3711,17 +3880,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { } // Download the FST file, to attempt to determine its model type - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = QNetworkRequest(url); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(networkRequest); - qCDebug(interfaceapp) << "Downloading avatar file at " << url; - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - QByteArray fstContents = reply->readAll(); - delete reply; - QVariantHash fstMapping = FSTReader::readMapping(fstContents); + QVariantHash fstMapping = FSTReader::downloadMapping(url); FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping); @@ -3732,26 +3891,27 @@ bool Application::askToSetAvatarUrl(const QString& url) { QPushButton* bodyButton = NULL; QPushButton* bodyAndHeadButton = NULL; + QString modelName = fstMapping["name"].toString(); QString message; QString typeInfo; switch (modelType) { case FSTReader::HEAD_MODEL: - message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar head?"); + message = QString("Would you like to use '") + modelName + QString("' for your avatar head?"); headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); break; case FSTReader::BODY_ONLY_MODEL: - message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar body?"); + message = QString("Would you like to use '") + modelName + QString("' for your avatar body?"); bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); break; case FSTReader::HEAD_AND_BODY_MODEL: - message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar?"); + message = QString("Would you like to use '") + modelName + QString("' for your avatar?"); bodyAndHeadButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); break; default: - message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for some part of your avatar head?"); + message = QString("Would you like to use '") + modelName + QString("' for some part of your avatar head?"); headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole); bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole); bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole); @@ -3764,34 +3924,18 @@ bool Application::askToSetAvatarUrl(const QString& url) { msgBox.exec(); if (msgBox.clickedButton() == headButton) { - qCDebug(interfaceapp) << "Chose to use for head: " << url; - _myAvatar->setFaceModelURL(url); - UserActivityLogger::getInstance().changedModel("head", url); - _myAvatar->sendIdentityPacket(); - emit faceURLChanged(url); + _myAvatar->useHeadURL(url, modelName); + emit headURLChanged(url, modelName); } else if (msgBox.clickedButton() == bodyButton) { - qCDebug(interfaceapp) << "Chose to use for body: " << url; - _myAvatar->setSkeletonModelURL(url); - // if the head is empty, reset it to the default head. - if (_myAvatar->getFaceModelURLString().isEmpty()) { - _myAvatar->setFaceModelURL(DEFAULT_HEAD_MODEL_URL); - emit faceURLChanged(DEFAULT_HEAD_MODEL_URL.toString()); - UserActivityLogger::getInstance().changedModel("head", DEFAULT_HEAD_MODEL_URL.toString()); - } - UserActivityLogger::getInstance().changedModel("skeleton", url); - _myAvatar->sendIdentityPacket(); - emit skeletonURLChanged(url); + _myAvatar->useBodyURL(url, modelName); + emit bodyURLChanged(url, modelName); } else if (msgBox.clickedButton() == bodyAndHeadButton) { - qCDebug(interfaceapp) << "Chose to use for body + head: " << url; - _myAvatar->setFaceModelURL(QString()); - _myAvatar->setSkeletonModelURL(url); - UserActivityLogger::getInstance().changedModel("skeleton", url); - _myAvatar->sendIdentityPacket(); - emit faceURLChanged(QString()); - emit skeletonURLChanged(url); + _myAvatar->useFullAvatarURL(url, modelName); + emit fullAvatarURLChanged(url, modelName); } else { qCDebug(interfaceapp) << "Declined to use the avatar: " << url; } + return true; } @@ -3881,6 +4025,9 @@ void Application::stopAllScripts(bool restart) { // stops all current running scripts for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); it != _scriptEnginesHash.constEnd(); it++) { + if (it.value()->isFinished()) { + continue; + } if (restart && it.value()->isUserLoaded()) { connect(it.value(), SIGNAL(finished(const QString&)), SLOT(loadScript(const QString&))); } @@ -4303,10 +4450,8 @@ void Application::checkSkeleton() { msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); - _myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL); - _myAvatar->sendIdentityPacket(); + _myAvatar->useBodyURL(DEFAULT_BODY_MODEL_URL, "Default"); } else { - _myAvatar->updateCharacterController(); _physicsEngine.setCharacterController(_myAvatar->getCharacterController()); } } @@ -4328,3 +4473,7 @@ void Application::friendsWindowClosed() { delete _friendsWindow; _friendsWindow = NULL; } + +void Application::postLambdaEvent(std::function f) { + QCoreApplication::postEvent(this, new LambdaEvent(f)); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index cf047f02d4..9f87d05711 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -117,9 +119,9 @@ static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html"; #ifdef Q_OS_WIN static const UINT UWM_IDENTIFY_INSTANCES = - RegisterWindowMessage("UWM_IDENTIFY_INSTANCES_{8AB82783-B74A-4258-955B-8188C22AA0D6}"); + RegisterWindowMessage("UWM_IDENTIFY_INSTANCES_{8AB82783-B74A-4258-955B-8188C22AA0D6}_" + qgetenv("USERNAME")); static const UINT UWM_SHOW_APPLICATION = - RegisterWindowMessage("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}"); + RegisterWindowMessage("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}_" + qgetenv("USERNAME")); #endif class Application; @@ -146,11 +148,14 @@ public: Application(int& argc, char** argv, QElapsedTimer &startup_time); ~Application(); + void postLambdaEvent(std::function f); + void loadScripts(); QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); void clearScriptsBeforeRunning(); void initializeGL(); + void initializeUi(); void paintGL(); void resizeGL(int width, int height); @@ -169,7 +174,8 @@ public: void touchUpdateEvent(QTouchEvent* event); void wheelEvent(QWheelEvent* event); - void dropEvent(QDropEvent *event); + void dropEvent(QDropEvent* event); + void dragEnterEvent(QDragEnterEvent* event); bool event(QEvent* event); bool eventFilter(QObject* object, QEvent* event); @@ -210,6 +216,7 @@ public: bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated; } FaceTracker* getActiveFaceTracker(); + QSystemTrayIcon* getTrayIcon() { return _trayIcon; } ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; } Overlays& getOverlays() { return _overlays; } @@ -335,8 +342,9 @@ signals: void checkBackgroundDownloads(); void domainConnectionRefused(const QString& reason); - void faceURLChanged(const QString& newValue); - void skeletonURLChanged(const QString& newValue); + void headURLChanged(const QString& newValue, const QString& modelName); + void bodyURLChanged(const QString& newValue, const QString& modelName); + void fullAvatarURLChanged(const QString& newValue, const QString& modelName); void beforeAboutToQuit(); @@ -382,6 +390,8 @@ public slots: void setVSyncEnabled(); void resetSensors(); + void setActiveFaceTracker(); + void aboutApp(); void showEditEntitiesHelp(); @@ -390,8 +400,6 @@ public slots: void notifyPacketVersionMismatch(); - void setActiveFaceTracker(); - void domainConnectionDenied(const QString& reason); private slots: diff --git a/interface/src/Bookmarks.cpp b/interface/src/Bookmarks.cpp index 4f022623dc..896a50acca 100644 --- a/interface/src/Bookmarks.cpp +++ b/interface/src/Bookmarks.cpp @@ -84,7 +84,7 @@ void Bookmarks::persistToFile() { saveFile.write(data); } -void Bookmarks::setupMenus(Menu* menubar, QMenu* menu) { +void Bookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { // Add menus/actions menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkLocation, 0, this, SLOT(bookmarkLocation())); @@ -192,7 +192,7 @@ void Bookmarks::enableMenuItems(bool enabled) { } void Bookmarks::addLocationToMenu(Menu* menubar, QString& name, QString& address) { - QAction* teleportAction = new QAction(_bookmarksMenu); + QAction* teleportAction = _bookmarksMenu->newAction(); teleportAction->setData(address); connect(teleportAction, SIGNAL(triggered()), this, SLOT(teleportToBookmark())); diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h index 59f9efb1b1..7ff9d48e8a 100644 --- a/interface/src/Bookmarks.h +++ b/interface/src/Bookmarks.h @@ -19,6 +19,7 @@ class QAction; class QMenu; class Menu; +class MenuWrapper; class Bookmarks: public QObject { Q_OBJECT @@ -26,7 +27,7 @@ class Bookmarks: public QObject { public: Bookmarks(); - void setupMenus(Menu* menubar, QMenu* menu); + void setupMenus(Menu* menubar, MenuWrapper* menu); private slots: void bookmarkLocation(); @@ -36,7 +37,7 @@ private slots: private: QVariantMap _bookmarks; // { name: address, ... } - QPointer _bookmarksMenu; + QPointer _bookmarksMenu; QPointer _deleteBookmarksAction; const QString BOOKMARKS_FILENAME = "bookmarks.json"; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index e334fd7c65..6c6d165e7d 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -55,7 +55,6 @@ Camera::Camera() : _farClip(DEFAULT_FAR_CLIP), // default _hmdPosition(), _hmdRotation(), - _scale(1.0f), _isKeepLookingAt(false), _lookingAt(0.0f, 0.0f, 0.0f) { @@ -94,8 +93,8 @@ void Camera::setHmdRotation(const glm::quat& hmdRotation) { } float Camera::getFarClip() const { - return (_scale * _farClip < std::numeric_limits::max()) - ? _scale * _farClip + return (_farClip < std::numeric_limits::max()) + ? _farClip : std::numeric_limits::max() - 1; } diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 7c6951b920..10572d3513 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -54,7 +54,6 @@ public: void setFarClip(float f); void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; } void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; } - void setScale(const float s) { _scale = s; } glm::quat getRotation() const { return _rotation * _hmdRotation; } const glm::vec3& getHmdPosition() const { return _hmdPosition; } @@ -63,11 +62,10 @@ public: CameraMode getMode() const { return _mode; } float getFieldOfView() const { return _fieldOfView; } float getAspectRatio() const { return _aspectRatio; } - float getNearClip() const { return _scale * _nearClip; } + float getNearClip() const { return _nearClip; } float getFarClip() const; const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; } const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; } - float getScale() const { return _scale; } public slots: QString getModeString() const; void setModeString(const QString& mode); @@ -107,7 +105,6 @@ private: glm::quat _rotation; glm::vec3 _hmdPosition; glm::quat _hmdRotation; - float _scale; bool _isKeepLookingAt; glm::vec3 _lookingAt; }; diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 6622a27145..230f4202c8 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -18,6 +18,7 @@ #include #include "DiscoverabilityManager.h" +#include "Menu.h" const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::All; @@ -96,4 +97,32 @@ void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discov emit discoverabilityModeChanged(discoverabilityMode); } -} \ No newline at end of file +} + +void DiscoverabilityManager::setVisibility() { + Menu* menu = Menu::getInstance(); + + if (menu->isOptionChecked(MenuOption::VisibleToEveryone)) { + this->setDiscoverabilityMode(Discoverability::All); + } else if (menu->isOptionChecked(MenuOption::VisibleToFriends)) { + this->setDiscoverabilityMode(Discoverability::Friends); + } else if (menu->isOptionChecked(MenuOption::VisibleToNoOne)) { + this->setDiscoverabilityMode(Discoverability::None); + } else { + qDebug() << "ERROR DiscoverabilityManager::setVisibility() called with unrecognized value."; + } +} + +void DiscoverabilityManager::visibilityChanged(Discoverability::Mode discoverabilityMode) { + Menu* menu = Menu::getInstance(); + + if (discoverabilityMode == Discoverability::All) { + menu->setIsOptionChecked(MenuOption::VisibleToEveryone, true); + } else if (discoverabilityMode == Discoverability::Friends) { + menu->setIsOptionChecked(MenuOption::VisibleToFriends, true); + } else if (discoverabilityMode == Discoverability::None) { + menu->setIsOptionChecked(MenuOption::VisibleToNoOne, true); + } else { + qDebug() << "ERROR DiscoverabilityManager::visibilityChanged() called with unrecognized value."; + } +} diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h index 9daa9408f8..1b5adcdb5d 100644 --- a/interface/src/DiscoverabilityManager.h +++ b/interface/src/DiscoverabilityManager.h @@ -36,6 +36,9 @@ public slots: Discoverability::Mode getDiscoverabilityMode() { return static_cast(_mode.get()); } void setDiscoverabilityMode(Discoverability::Mode discoverabilityMode); + void setVisibility(); + void visibilityChanged(Discoverability::Mode discoverabilityMode); + signals: void discoverabilityModeChanged(Discoverability::Mode discoverabilityMode); diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 12a10681ce..9a9512a0b0 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -82,30 +82,6 @@ void GLCanvas::resizeGL(int width, int height) { Application::getInstance()->resizeGL(width, height); } -void GLCanvas::keyPressEvent(QKeyEvent* event) { - Application::getInstance()->keyPressEvent(event); -} - -void GLCanvas::keyReleaseEvent(QKeyEvent* event) { - Application::getInstance()->keyReleaseEvent(event); -} - -void GLCanvas::focusOutEvent(QFocusEvent* event) { - Application::getInstance()->focusOutEvent(event); -} - -void GLCanvas::mouseMoveEvent(QMouseEvent* event) { - Application::getInstance()->mouseMoveEvent(event); -} - -void GLCanvas::mousePressEvent(QMouseEvent* event) { - Application::getInstance()->mousePressEvent(event); -} - -void GLCanvas::mouseReleaseEvent(QMouseEvent* event) { - Application::getInstance()->mouseReleaseEvent(event); -} - void GLCanvas::activeChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationActive: @@ -139,6 +115,7 @@ void GLCanvas::throttleRender() { OculusManager::beginFrameTiming(); } + makeCurrent(); Application::getInstance()->paintGL(); swapBuffers(); @@ -151,40 +128,37 @@ void GLCanvas::throttleRender() { int updateTime = 0; bool GLCanvas::event(QEvent* event) { switch (event->type()) { + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::Resize: case QEvent::TouchBegin: - Application::getInstance()->touchBeginEvent(static_cast(event)); - event->accept(); - return true; case QEvent::TouchEnd: - Application::getInstance()->touchEndEvent(static_cast(event)); - return true; case QEvent::TouchUpdate: - Application::getInstance()->touchUpdateEvent(static_cast(event)); - return true; + case QEvent::Wheel: + case QEvent::DragEnter: + case QEvent::Drop: + if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { + return true; + } + break; + case QEvent::Paint: + // Ignore paint events that occur after we've decided to quit + if (Application::getInstance()->isAboutToQuit()) { + return true; + } + break; + default: break; } return QGLWidget::event(event); } -void GLCanvas::wheelEvent(QWheelEvent* event) { - Application::getInstance()->wheelEvent(event); -} - -void GLCanvas::dragEnterEvent(QDragEnterEvent* event) { - const QMimeData* mimeData = event->mimeData(); - foreach (QUrl url, mimeData->urls()) { - auto urlString = url.toString(); - if (Application::getInstance()->canAcceptURL(urlString)) { - event->acceptProposedAction(); - break; - } - } -} - -void GLCanvas::dropEvent(QDropEvent* event) { - Application::getInstance()->dropEvent(event); -} // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the // SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 7b86f983e9..6c53a17e04 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -40,23 +40,8 @@ protected: virtual void initializeGL(); virtual void paintGL(); virtual void resizeGL(int width, int height); - - virtual void keyPressEvent(QKeyEvent* event); - virtual void keyReleaseEvent(QKeyEvent* event); - - virtual void focusOutEvent(QFocusEvent* event); - - virtual void mouseMoveEvent(QMouseEvent* event); - virtual void mousePressEvent(QMouseEvent* event); - virtual void mouseReleaseEvent(QMouseEvent* event); - virtual bool event(QEvent* event); - virtual void wheelEvent(QWheelEvent* event); - - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void dropEvent(QDropEvent* event); - private slots: void activeChanged(Qt::ApplicationState state); void throttleRender(); diff --git a/interface/src/MainWindow.cpp b/interface/src/MainWindow.cpp index 41b1249af1..f60716dc48 100644 --- a/interface/src/MainWindow.cpp +++ b/interface/src/MainWindow.cpp @@ -55,6 +55,10 @@ void MainWindow::saveGeometry() { } } +void MainWindow::closeEvent(QCloseEvent* event) { + qApp->quit(); +} + void MainWindow::moveEvent(QMoveEvent* event) { emit windowGeometryChanged(QRect(event->pos(), size())); QMainWindow::moveEvent(event); diff --git a/interface/src/MainWindow.h b/interface/src/MainWindow.h index 4437fa6a1f..c6faf8e01a 100644 --- a/interface/src/MainWindow.h +++ b/interface/src/MainWindow.h @@ -30,6 +30,7 @@ signals: void windowShown(bool shown); protected: + virtual void closeEvent(QCloseEvent* event); virtual void moveEvent(QMoveEvent* event); virtual void resizeEvent(QResizeEvent* event); virtual void showEvent(QShowEvent* event); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1cce0b1f56..90a9d3b22c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "Application.h" #include "AccountManager.h" @@ -30,7 +31,6 @@ #include "devices/Faceshift.h" #include "devices/RealSense.h" #include "devices/SixenseManager.h" -#include "devices/Visage.h" #include "MainWindow.h" #include "scripting/MenuScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) @@ -53,7 +53,6 @@ Menu* Menu::getInstance() { if (!_instance) { qCDebug(interfaceapp, "First call to Menu::getInstance() - initing menu."); - _instance = new Menu(); } @@ -63,8 +62,7 @@ Menu* Menu::getInstance() { } Menu::Menu() { - QMenu* fileMenu = addMenu("File"); - + MenuWrapper * fileMenu = addMenu("File"); #ifdef Q_OS_MAC addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole); #endif @@ -97,7 +95,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(fileMenu, MenuOption::AddressBar, - Qt::Key_Enter, + Qt::CTRL | Qt::Key_L, dialogsManager.data(), SLOT(toggleAddressBar())); auto addressManager = DependencyManager::get(); @@ -114,7 +112,7 @@ Menu::Menu() { QAction::QuitRole); - QMenu* editMenu = addMenu("Edit"); + MenuWrapper* editMenu = addMenu("Edit"); QUndoStack* undoStack = qApp->getUndoStack(); QAction* undoAction = undoStack->createUndoAction(editMenu); @@ -137,7 +135,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, dialogsManager.data(), SLOT(editAnimations())); - QMenu* toolsMenu = addMenu("Tools"); + MenuWrapper* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, dialogsManager.data(), SLOT(showScriptEditor())); @@ -151,33 +149,34 @@ Menu::Menu() { connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, Qt::Key_Backslash, + addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, + 0, // QML Qt::Key_Backslash, dialogsManager.data(), SLOT(showIRCLink())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::AddRemoveFriends, 0, qApp, SLOT(showFriendsWindow())); - QMenu* visibilityMenu = toolsMenu->addMenu("I Am Visible To"); + MenuWrapper* visibilityMenu = toolsMenu->addMenu("I Am Visible To"); { QActionGroup* visibilityGroup = new QActionGroup(toolsMenu); auto discoverabilityManager = DependencyManager::get(); QAction* visibleToEveryone = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToEveryone, 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::All, - this, SLOT(setVisibility())); + discoverabilityManager.data(), SLOT(setVisibility())); visibilityGroup->addAction(visibleToEveryone); QAction* visibleToFriends = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToFriends, 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::Friends, - this, SLOT(setVisibility())); + discoverabilityManager.data(), SLOT(setVisibility())); visibilityGroup->addAction(visibleToFriends); QAction* visibleToNoOne = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToNoOne, 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::None, - this, SLOT(setVisibility())); + discoverabilityManager.data(), SLOT(setVisibility())); visibilityGroup->addAction(visibleToNoOne); connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, - this, &Menu::visibilityChanged); + discoverabilityManager.data(), &DiscoverabilityManager::visibilityChanged); } addActionToQMenuAndActionHash(toolsMenu, @@ -194,30 +193,30 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::ResetSensors, - Qt::Key_Apostrophe, + 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); - QMenu* avatarMenu = addMenu("Avatar"); + MenuWrapper* avatarMenu = addMenu("Avatar"); QObject* avatar = DependencyManager::get()->getMyAvatar(); - QMenu* avatarSizeMenu = avatarMenu->addMenu("Size"); + MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::IncreaseAvatarSize, - Qt::Key_Plus, + 0, // QML Qt::Key_Plus, avatar, SLOT(increaseSize())); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::DecreaseAvatarSize, - Qt::Key_Minus, + 0, // QML Qt::Key_Minus, avatar, SLOT(decreaseSize())); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::ResetAvatarSize, - Qt::Key_Equal, + 0, // QML Qt::Key_Equal, avatar, SLOT(resetSize())); @@ -233,35 +232,39 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ShiftHipsForIdleAnimations, 0, false, avatar, SLOT(updateMotionBehavior())); - QMenu* viewMenu = addMenu("View"); + MenuWrapper* viewMenu = addMenu("View"); + addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::Fullscreen, #ifdef Q_OS_MAC - addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::Fullscreen, Qt::CTRL | Qt::META | Qt::Key_F, - false, - qApp, - SLOT(setFullscreen(bool))); #else - addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::Fullscreen, Qt::CTRL | Qt::Key_F, +#endif false, qApp, SLOT(setFullscreen(bool))); -#endif - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true, - qApp,SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, - qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, Qt::META | Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, + 0, // QML Qt::Key_P, + true, qApp, SLOT(cameraMenuChanged())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, + 0, //QML Qt::SHIFT | Qt::Key_H, + true); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, + 0, // QML Qt::Key_H, + false, qApp, SLOT(cameraMenuChanged())); + + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, +#ifdef Q_OS_MAC + Qt::META | Qt::Key_H, +#else + Qt::CTRL | Qt::Key_H, +#endif false, dialogsManager.data(), SLOT(hmdTools(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::EnableVRMode, 0, false, qApp, @@ -273,7 +276,7 @@ Menu::Menu() { SLOT(setEnable3DTVMode(bool))); - QMenu* nodeBordersMenu = viewMenu->addMenu("Server Borders"); + MenuWrapper* nodeBordersMenu = viewMenu->addMenu("Server Borders"); NodeBounds& nodeBounds = qApp->getNodeBoundsDisplay(); addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersEntityNodes, Qt::CTRL | Qt::SHIFT | Qt::Key_1, false, @@ -282,23 +285,26 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, false); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); - addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, qApp, SLOT(toggleLogDialog())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); + addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, + Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, dialogsManager.data(), SLOT(octreeStatsDetails())); - QMenu* developerMenu = addMenu("Developer"); + MenuWrapper* developerMenu = addMenu("Developer"); - QMenu* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true); + MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, + 0, // QML Qt::SHIFT | Qt::Key_A, + true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges); - QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); + MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu); ambientLightGroup->setExclusive(true); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLightGlobal, 0, true)); @@ -313,14 +319,14 @@ Menu::Menu() { ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight8, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight9, 0, false)); - QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows"); + MenuWrapper* shadowMenu = renderOptionsMenu->addMenu("Shadows"); QActionGroup* shadowGroup = new QActionGroup(shadowMenu); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false)); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false)); { - QMenu* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate); + MenuWrapper* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate); QActionGroup* framerateGroup = new QActionGroup(framerateMenu); framerateGroup->setExclusive(true); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerateUnlimited, 0, true)); @@ -337,7 +343,7 @@ Menu::Menu() { } - QMenu* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); + MenuWrapper* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); resolutionGroup->setExclusive(true); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true)); @@ -346,17 +352,20 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, + 0, // QML Qt::Key_Asterisk, + true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true, DependencyManager::get().data(), SLOT(toggleGlowEffect(bool))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Wireframe, Qt::ALT | Qt::Key_W, false); - addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, - dialogsManager.data(), SLOT(lodTools())); + addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, + 0, // QML Qt::SHIFT | Qt::Key_L, + dialogsManager.data(), SLOT(lodTools())); - QMenu* avatarDebugMenu = developerMenu->addMenu("Avatar"); + MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); - QMenu* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); + MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); { QActionGroup* faceTrackerGroup = new QActionGroup(avatarDebugMenu); @@ -371,19 +380,24 @@ Menu::Menu() { qApp, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(faceshiftFaceTracker); #endif - - QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::DDEFaceRegression, +#ifdef HAVE_DDE + QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, 0, false, qApp, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(ddeFaceTracker); - -#ifdef HAVE_VISAGE - QAction* visageFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::Visage, - 0, false, - qApp, SLOT(setActiveFaceTracker())); - faceTrackerGroup->addAction(visageFaceTracker); #endif } +#ifdef HAVE_DDE + faceTrackingMenu->addSeparator(); + QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true); + useAudioForMouth->setVisible(false); + QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true); + ddeFiltering->setVisible(false); +#endif + + auto avatarManager = DependencyManager::get(); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, + avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes); @@ -391,14 +405,14 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); - QMenu* handOptionsMenu = developerMenu->addMenu("Hands"); + MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); - QMenu* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); + MenuWrapper* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); #ifdef __APPLE__ addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseEnabled, @@ -421,7 +435,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseLasers, 0, false); - QMenu* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); + MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false); #ifdef HAVE_RSSDK @@ -430,7 +444,7 @@ Menu::Menu() { RealSense::getInstance(), SLOT(loadRSSDKFile())); #endif - QMenu* networkMenu = developerMenu->addMenu("Network"); + MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, @@ -443,8 +457,8 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, dialogsManager.data(), SLOT(toggleDiskCacheEditor())); - QMenu* timingMenu = developerMenu->addMenu("Timing and Stats"); - QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); + MenuWrapper* timingMenu = developerMenu->addMenu("Timing and Stats"); + MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); @@ -460,7 +474,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); auto audioIO = DependencyManager::get(); - QMenu* audioDebugMenu = developerMenu->addMenu("Audio"); + MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, @@ -471,8 +485,6 @@ Menu::Menu() { audioIO.data(), SLOT(toggleServerEcho())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, audioIO.data(), SLOT(toggleLocalEcho())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::StereoAudio, 0, false, - audioIO.data(), SLOT(toggleStereoInput())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, @@ -483,34 +495,10 @@ Menu::Menu() { 0, audioIO.data(), SLOT(sendMuteEnvironmentPacket())); - - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioSourceInject, - 0, - false, - audioIO.data(), - SLOT(toggleAudioSourceInject())); - QMenu* audioSourceMenu = audioDebugMenu->addMenu("Generated Audio Source"); - { - QAction *pinkNoise = addCheckableActionToQMenuAndActionHash(audioSourceMenu, MenuOption::AudioSourcePinkNoise, - 0, - false, - audioIO.data(), - SLOT(selectAudioSourcePinkNoise())); - - QAction *sine440 = addCheckableActionToQMenuAndActionHash(audioSourceMenu, MenuOption::AudioSourceSine440, - 0, - true, - audioIO.data(), - SLOT(selectAudioSourceSine440())); - - QActionGroup* audioSourceGroup = new QActionGroup(audioSourceMenu); - audioSourceGroup->addAction(pinkNoise); - audioSourceGroup->addAction(sine440); - } auto scope = DependencyManager::get(); - QMenu* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); + MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, scope.data(), @@ -559,7 +547,12 @@ Menu::Menu() { statsRenderer.data(), SLOT(toggleShowInjectedStreams())); - QMenu* helpMenu = addMenu("Help"); + + MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + + MenuWrapper* helpMenu = addMenu("Help"); addActionToQMenuAndActionHash(helpMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); #ifndef Q_OS_MAC @@ -605,7 +598,7 @@ void Menu::scanMenu(QMenu& menu, settingsAction modifySetting, Settings& setting settings.endGroup(); } -void Menu::addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName, int menuItemLocation) { +void Menu::addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName, int menuItemLocation) { QAction* actionBefore = NULL; if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) { actionBefore = destinationMenu->actions()[menuItemLocation]; @@ -625,7 +618,7 @@ void Menu::addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& } } -QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu, +QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut, const QObject* receiver, @@ -662,7 +655,7 @@ QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu, return action; } -QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu, +QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu, QAction* action, const QString& actionName, const QKeySequence& shortcut, @@ -697,7 +690,7 @@ QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu, return action; } -QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, +QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut, const bool checked, @@ -713,7 +706,7 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, return action; } -void Menu::removeAction(QMenu* menu, const QString& actionName) { +void Menu::removeAction(MenuWrapper* menu, const QString& actionName) { menu->removeAction(_actionHash.value(actionName)); _actionHash.remove(actionName); } @@ -752,7 +745,7 @@ QAction* Menu::getActionForOption(const QString& menuOption) { return _actionHash.value(menuOption); } -QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { +QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) { QList menuActions; if (menu) { menuActions = menu->actions(); @@ -761,6 +754,7 @@ QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { } foreach (QAction* menuAction, menuActions) { + QString actionText = menuAction->text(); if (menuName == menuAction->text()) { return menuAction; } @@ -768,18 +762,18 @@ QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { return NULL; } -QMenu* Menu::getSubMenuFromName(const QString& menuName, QMenu* menu) { +MenuWrapper* Menu::getSubMenuFromName(const QString& menuName, MenuWrapper* menu) { QAction* action = getActionFromName(menuName, menu); if (action) { - return action->menu(); + return MenuWrapper::fromMenu(action->menu()); } return NULL; } -QMenu* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) { +MenuWrapper* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) { QStringList menuTree = menuName.split(">"); - QMenu* parent = NULL; - QMenu* menu = NULL; + MenuWrapper* parent = NULL; + MenuWrapper* menu = NULL; foreach (QString menuTreePart, menuTree) { parent = menu; finalMenuPart = menuTreePart.trimmed(); @@ -791,10 +785,10 @@ QMenu* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) { return parent; } -QMenu* Menu::getMenu(const QString& menuName) { +MenuWrapper* Menu::getMenu(const QString& menuName) { QStringList menuTree = menuName.split(">"); - QMenu* parent = NULL; - QMenu* menu = NULL; + MenuWrapper* parent = NULL; + MenuWrapper* menu = NULL; int item = 0; foreach (QString menuTreePart, menuTree) { menu = getSubMenuFromName(menuTreePart.trimmed(), parent); @@ -809,19 +803,19 @@ QMenu* Menu::getMenu(const QString& menuName) { QAction* Menu::getMenuAction(const QString& menuName) { QStringList menuTree = menuName.split(">"); - QMenu* parent = NULL; + MenuWrapper* parent = NULL; QAction* action = NULL; foreach (QString menuTreePart, menuTree) { action = getActionFromName(menuTreePart.trimmed(), parent); if (!action) { break; } - parent = action->menu(); + parent = MenuWrapper::fromMenu(action->menu()); } return action; } -int Menu::findPositionOfMenuItem(QMenu* menu, const QString& searchMenuItem) { +int Menu::findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem) { int position = 0; foreach(QAction* action, menu->actions()) { if (action->text() == searchMenuItem) { @@ -832,7 +826,7 @@ int Menu::findPositionOfMenuItem(QMenu* menu, const QString& searchMenuItem) { return UNSPECIFIED_POSITION; // not found } -int Menu::positionBeforeSeparatorIfNeeded(QMenu* menu, int requestedPosition) { +int Menu::positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition) { QList menuActions = menu->actions(); if (requestedPosition > 1 && requestedPosition < menuActions.size()) { QAction* beforeRequested = menuActions[requestedPosition - 1]; @@ -844,15 +838,15 @@ int Menu::positionBeforeSeparatorIfNeeded(QMenu* menu, int requestedPosition) { } -QMenu* Menu::addMenu(const QString& menuName) { +MenuWrapper* Menu::addMenu(const QString& menuName) { QStringList menuTree = menuName.split(">"); - QMenu* addTo = NULL; - QMenu* menu = NULL; + MenuWrapper* addTo = NULL; + MenuWrapper* menu = NULL; foreach (QString menuTreePart, menuTree) { menu = getSubMenuFromName(menuTreePart.trimmed(), addTo); if (!menu) { if (!addTo) { - menu = QMenuBar::addMenu(menuTreePart.trimmed()); + menu = new MenuWrapper(QMenuBar::addMenu(menuTreePart.trimmed())); } else { menu = addTo->addMenu(menuTreePart.trimmed()); } @@ -870,7 +864,7 @@ void Menu::removeMenu(const QString& menuName) { // only proceed if the menu actually exists if (action) { QString finalMenuPart; - QMenu* parent = getMenuParent(menuName, finalMenuPart); + MenuWrapper* parent = getMenuParent(menuName, finalMenuPart); if (parent) { parent->removeAction(action); } else { @@ -892,14 +886,14 @@ bool Menu::menuExists(const QString& menuName) { } void Menu::addSeparator(const QString& menuName, const QString& separatorName) { - QMenu* menuObj = getMenu(menuName); + MenuWrapper* menuObj = getMenu(menuName); if (menuObj) { addDisabledActionAndSeparator(menuObj, separatorName); } } void Menu::removeSeparator(const QString& menuName, const QString& separatorName) { - QMenu* menu = getMenu(menuName); + MenuWrapper* menu = getMenu(menuName); bool separatorRemoved = false; if (menu) { int textAt = findPositionOfMenuItem(menu, separatorName); @@ -922,7 +916,7 @@ void Menu::removeSeparator(const QString& menuName, const QString& separatorName } void Menu::addMenuItem(const MenuItemProperties& properties) { - QMenu* menuObj = getMenu(properties.menuName); + MenuWrapper* menuObj = getMenu(properties.menuName); if (menuObj) { QShortcut* shortcut = NULL; if (!properties.shortcutKeySequence.isEmpty()) { @@ -963,7 +957,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { } void Menu::removeMenuItem(const QString& menu, const QString& menuitem) { - QMenu* menuObj = getMenu(menu); + MenuWrapper* menuObj = getMenu(menu); if (menuObj) { removeAction(menuObj, menuitem); QMenuBar::repaint(); @@ -978,28 +972,62 @@ bool Menu::menuItemExists(const QString& menu, const QString& menuitem) { return false; }; -void Menu::setVisibility() { - auto discoverabilityManager = DependencyManager::get(); - if (Menu::getInstance()->isOptionChecked(MenuOption::VisibleToEveryone)) { - discoverabilityManager->setDiscoverabilityMode(Discoverability::All); - } else if (Menu::getInstance()->isOptionChecked(MenuOption::VisibleToFriends)) { - discoverabilityManager->setDiscoverabilityMode(Discoverability::Friends); - } else if (Menu::getInstance()->isOptionChecked(MenuOption::VisibleToNoOne)) { - discoverabilityManager->setDiscoverabilityMode(Discoverability::None); - } else { - qCDebug(interfaceapp) << "ERROR Menu::setVisibility() called with unrecognized value."; - } +MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) { + VrMenu::executeOrQueue([=](VrMenu* vrMenu) { + vrMenu->addMenu(menu); + }); + _backMap[menu] = this; } -void Menu::visibilityChanged(Discoverability::Mode discoverabilityMode) { - if (discoverabilityMode == Discoverability::All) { - setIsOptionChecked(MenuOption::VisibleToEveryone, true); - } else if (discoverabilityMode == Discoverability::Friends) { - setIsOptionChecked(MenuOption::VisibleToFriends, true); - } else if (discoverabilityMode == Discoverability::None) { - setIsOptionChecked(MenuOption::VisibleToNoOne, true); - } else { - qCDebug(interfaceapp) << "ERROR Menu::visibilityChanged() called with unrecognized value."; - } +QList MenuWrapper::actions() { + return _realMenu->actions(); } + +MenuWrapper* MenuWrapper::addMenu(const QString& menuName) { + return new MenuWrapper(_realMenu->addMenu(menuName)); +} + +void MenuWrapper::setEnabled(bool enabled) { + _realMenu->setEnabled(enabled); +} + +void MenuWrapper::addSeparator() { + _realMenu->addSeparator(); +} + +void MenuWrapper::addAction(QAction* action) { + _realMenu->addAction(action); + VrMenu::executeOrQueue([=](VrMenu* vrMenu) { + vrMenu->addAction(_realMenu, action); + }); +} + +QAction* MenuWrapper::addAction(const QString& menuName) { + QAction* action = _realMenu->addAction(menuName); + VrMenu::executeOrQueue([=](VrMenu* vrMenu) { + vrMenu->addAction(_realMenu, action); + }); + return action; +} + +QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) { + QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut); + VrMenu::executeOrQueue([=](VrMenu* vrMenu) { + vrMenu->addAction(_realMenu, action); + }); + return action; +} + +void MenuWrapper::removeAction(QAction* action) { + _realMenu->removeAction(action); +} + +void MenuWrapper::insertAction(QAction* before, QAction* action) { + _realMenu->insertAction(before, action); + VrMenu::executeOrQueue([=](VrMenu* vrMenu) { + vrMenu->insertAction(before, action); + }); +} + +QHash MenuWrapper::_backMap; diff --git a/interface/src/Menu.h b/interface/src/Menu.h index db126a3c9b..7d105687ab 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -25,6 +25,35 @@ class Settings; +class MenuWrapper : public QObject { +public: + QList actions(); + MenuWrapper* addMenu(const QString& menuName); + void setEnabled(bool enabled = true); + void addSeparator(); + void addAction(QAction* action); + + QAction* addAction(const QString& menuName); + void insertAction(QAction* before, QAction* menuName); + + QAction* addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut = 0); + void removeAction(QAction* action); + + QAction* newAction() { + return new QAction(_realMenu); + } +private: + MenuWrapper(QMenu* menu); + + static MenuWrapper* fromMenu(QMenu* menu) { + return _backMap[menu]; + } + + QMenu* const _realMenu; + static QHash _backMap; + friend class Menu; +}; + class Menu : public QMenuBar { Q_OBJECT public: @@ -33,29 +62,29 @@ public: void loadSettings(); void saveSettings(); - QMenu* getMenu(const QString& menuName); + MenuWrapper* getMenu(const QString& menuName); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); - QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, + QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, const QObject* receiver = NULL, const char* member = NULL, QAction::MenuRole role = QAction::NoRole, int menuItemLocation = UNSPECIFIED_POSITION); - QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, + QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu, QAction* action, const QString& actionName = QString(), const QKeySequence& shortcut = 0, QAction::MenuRole role = QAction::NoRole, int menuItemLocation = UNSPECIFIED_POSITION); - void removeAction(QMenu* menu, const QString& actionName); + void removeAction(MenuWrapper* menu, const QString& actionName); public slots: - QMenu* addMenu(const QString& menuName); + MenuWrapper* addMenu(const QString& menuName); void removeMenu(const QString& menuName); bool menuExists(const QString& menuName); void addSeparator(const QString& menuName, const QString& separatorName); @@ -66,9 +95,6 @@ public slots: bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); -private slots: - void setVisibility(); - private: static Menu* _instance; Menu(); @@ -80,10 +106,10 @@ private: void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings); /// helper method to have separators with labels that are also compatible with OS X - void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName, + void addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName, int menuItemLocation = UNSPECIFIED_POSITION); - QAction* addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, + QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, const bool checked = false, @@ -91,15 +117,13 @@ private: const char* member = NULL, int menuItemLocation = UNSPECIFIED_POSITION); - QAction* getActionFromName(const QString& menuName, QMenu* menu); - QMenu* getSubMenuFromName(const QString& menuName, QMenu* menu); - QMenu* getMenuParent(const QString& menuName, QString& finalMenuPart); + QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); + MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); + MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); QAction* getMenuAction(const QString& menuName); - int findPositionOfMenuItem(QMenu* menu, const QString& searchMenuItem); - int positionBeforeSeparatorIfNeeded(QMenu* menu, int requestedPosition); - - void visibilityChanged(Discoverability::Mode discoverabilityMode); + int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem); + int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition); QHash _actionHash; }; @@ -123,9 +147,7 @@ namespace MenuOption { const QString AudioScopeTwentyFrames = "Twenty"; const QString AudioStats = "Audio Stats"; const QString AudioStatsShowInjectedStreams = "Audio Stats Show Injected Streams"; - const QString AudioSourceInject = "Generated Audio"; - const QString AudioSourcePinkNoise = "Pink Noise"; - const QString AudioSourceSine440 = "Sine 440hz"; + const QString AvatarReceiveStats = "Show Receive Stats"; const QString BandwidthDetails = "Bandwidth Details"; const QString BlueSpeechSphere = "Blue Sphere While Speaking"; const QString BookmarkLocation = "Bookmark Location"; @@ -138,7 +160,6 @@ namespace MenuOption { const QString ControlWithSpeech = "Control With Speech"; const QString CopyAddress = "Copy Address to Clipboard"; const QString CopyPath = "Copy Path to Clipboard"; - const QString DDEFaceRegression = "DDE Face Regression"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; @@ -194,7 +215,10 @@ namespace MenuOption { const QString OctreeStats = "Entity Statistics"; const QString OffAxisProjection = "Off-Axis Projection"; const QString OnlyDisplayTopTen = "Only Display Top Ten"; + const QString PackageModel = "Package Model..."; const QString Pair = "Pair"; + const QString PhysicsShowOwned = "Highlight Simulation Ownership"; + const QString PhysicsShowHulls = "Draw Collision Hulls"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; const QString Preferences = "Preferences..."; const QString Quit = "Quit"; @@ -244,15 +268,15 @@ namespace MenuOption { const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations"; const QString Stars = "Stars"; const QString Stats = "Stats"; - const QString StereoAudio = "Stereo Audio (disables spatial sound)"; const QString StopAllScripts = "Stop All Scripts"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString TestPing = "Test Ping"; const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; - const QString PackageModel = "Package Model..."; - const QString Visage = "Visage"; + const QString UseAudioForMouth = "Use Audio for Mouth"; + const QString UseCamera = "Use Camera"; + const QString VelocityFilter = "Velocity Filter"; const QString VisibleToEveryone = "Everyone"; const QString VisibleToFriends = "Friends"; const QString VisibleToNoOne = "No one"; diff --git a/interface/src/ScriptsModel.h b/interface/src/ScriptsModel.h index 914a197201..ae75acfa3b 100644 --- a/interface/src/ScriptsModel.h +++ b/interface/src/ScriptsModel.h @@ -50,7 +50,7 @@ public: TreeNodeScript(const QString& localPath, const QString& fullPath, ScriptOrigin origin); const QString& getLocalPath() { return _localPath; } const QString& getFullPath() { return _fullPath; }; - const ScriptOrigin getOrigin() { return _origin; }; + ScriptOrigin getOrigin() { return _origin; }; private: QString _localPath; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 63dd884bc6..05f834255c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -581,7 +581,8 @@ void Avatar::renderBillboard() { glm::vec2 texCoordTopLeft(0.0f, 0.0f); glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, + glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); glPopMatrix(); @@ -709,11 +710,24 @@ void Avatar::renderDisplayName() { glm::vec4(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA)); glm::vec4 color(0.93f, 0.93f, 0.93f, _displayNameAlpha); + + // optionally render timing stats for this avatar with the display name + QString renderedDisplayName = _displayName; + + if (DependencyManager::get()->shouldShowReceiveStats()) { + const float BYTES_PER_KILOBYTE = 1000.0f; + float kilobytesPerSecond = getAverageBytesReceivedPerSecond() / BYTES_PER_KILOBYTE; + + renderedDisplayName += QString(" - (%1 KBps, %2 Hz)") + .arg(QString::number(kilobytesPerSecond, 'f', 2)) + .arg(getReceiveRate()); + } + QByteArray ba = _displayName.toLocal8Bit(); const char* text = ba.data(); glDisable(GL_POLYGON_OFFSET_FILL); - textRenderer(DISPLAYNAME)->draw(text_x, text_y, text, color); + textRenderer(DISPLAYNAME)->draw(text_x, text_y, renderedDisplayName, color); glPopMatrix(); @@ -1058,3 +1072,20 @@ void Avatar::setShowDisplayName(bool showDisplayName) { } +// virtual +void Avatar::rebuildSkeletonBody() { + /* TODO: implement this and remove override from MyAvatar (when we have AvatarMotionStates working) + if (_motionState) { + // compute localAABox + const CapsuleShape& capsule = _skeletonModel.getBoundingShape(); + float radius = capsule.getRadius(); + float height = 2.0f * (capsule.getHalfHeight() + radius); + glm::vec3 corner(-radius, -0.5f * height, -radius); + corner += _skeletonModel.getBoundingShapeOffset(); + glm::vec3 scale(2.0f * radius, height, 2.0f * radius); + //_characterController.setLocalBoundingBox(corner, scale); + _motionState->setBoundingBox(corner, scale); + } + */ +} + diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 032fe25c7d..a358e7e58d 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -162,6 +162,8 @@ public: // (otherwise floating point error will cause problems at large positions). void applyPositionDelta(const glm::vec3& delta); + virtual void rebuildSkeletonBody(); + signals: void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision); @@ -228,6 +230,8 @@ private: static int _jointConesID; int _voiceSphereID; + + //AvatarMotionState* _motionState = nullptr; }; #endif // hifi_Avatar_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5ee09ba1cf..38144dfe5f 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -149,7 +149,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { while (fadingIterator != _avatarFades.end()) { Avatar* avatar = static_cast(fadingIterator->data()); - avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); + avatar->setTargetScale(avatar->getScale() * SHRINK_RATE, true); if (avatar->getTargetScale() < MIN_FADE_SCALE) { fadingIterator = _avatarFades.erase(fadingIterator); } else { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 4912fabd33..3c7f7296fe 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -40,7 +40,9 @@ public: void renderAvatars(RenderArgs::RenderMode renderMode, bool postLighting = false, bool selfAvatarOnly = false); void clearOtherAvatars(); - + + bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } + class LocalLight { public: glm::vec3 color; @@ -49,7 +51,10 @@ public: Q_INVOKABLE void setLocalLights(const QVector& localLights); Q_INVOKABLE QVector getLocalLights() const; - + +public slots: + void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } + private: AvatarManager(QObject* parent = 0); AvatarManager(const AvatarManager& other); @@ -59,7 +64,7 @@ private: AvatarSharedPointer newSharedAvatar(); - // virtual override + // virtual overrides AvatarHash::iterator erase(const AvatarHash::iterator& iterator); QVector _avatarFades; @@ -67,6 +72,8 @@ private: quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. QVector _localLights; + + bool _shouldShowReceiveStats = false; }; Q_DECLARE_METATYPE(AvatarManager::LocalLight) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index b90b693139..41c2e9b54c 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -73,25 +73,6 @@ void Head::reset() { } void Head::simulate(float deltaTime, bool isMine, bool billboard) { - if (isMine) { - MyAvatar* myAvatar = static_cast(_owningAvatar); - - // Only use face trackers when not playing back a recording. - if (!myAvatar->isPlaying()) { - FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker(); - _isFaceTrackerConnected = faceTracker != NULL; - if (_isFaceTrackerConnected) { - _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); - } - } - // Twist the upper body to follow the rotation of the head, but only do this with my avatar, - // since everyone else will see the full joint rotations for other people. - const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; - const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; - float currentTwist = getTorsoTwist(); - setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); - } - // Update audio trailing average for rendering facial animations const float AUDIO_AVERAGING_SECS = 0.05f; const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f; @@ -102,7 +83,44 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { } else { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - + + if (isMine) { + MyAvatar* myAvatar = static_cast(_owningAvatar); + + // Only use face trackers when not playing back a recording. + if (!myAvatar->isPlaying()) { + FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker(); + _isFaceTrackerConnected = faceTracker != NULL; + if (_isFaceTrackerConnected) { + _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); + + if (typeid(*faceTracker) == typeid(DdeFaceTracker) + && Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) { + + calculateMouthShapes(); + + const int JAW_OPEN_BLENDSHAPE = 21; + const int MMMM_BLENDSHAPE = 34; + const int FUNNEL_BLENDSHAPE = 40; + const int SMILE_LEFT_BLENDSHAPE = 28; + const int SMILE_RIGHT_BLENDSHAPE = 29; + _blendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen; + _blendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4; + _blendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4; + _blendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2; + _blendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3; + + } + } + } + // Twist the upper body to follow the rotation of the head, but only do this with my avatar, + // since everyone else will see the full joint rotations for other people. + const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; + const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; + float currentTwist = getTorsoTwist(); + setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); + } + if (!(_isFaceTrackerConnected || billboard)) { // Update eye saccades const float AVERAGE_MICROSACCADE_INTERVAL = 0.50f; @@ -177,33 +195,7 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { } // use data to update fake Faceshift blendshape coefficients - - const float JAW_OPEN_SCALE = 0.015f; - const float JAW_OPEN_RATE = 0.9f; - const float JAW_CLOSE_RATE = 0.90f; - float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE; - if (audioDelta > _audioJawOpen) { - _audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE; - } else { - _audioJawOpen *= JAW_CLOSE_RATE; - } - _audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f); - - // _mouth2 = "mmmm" shape - // _mouth3 = "funnel" shape - // _mouth4 = "smile" shape - const float FUNNEL_PERIOD = 0.985f; - const float FUNNEL_RANDOM_PERIOD = 0.01f; - const float MMMM_POWER = 0.25f; - const float MMMM_PERIOD = 0.91f; - const float MMMM_RANDOM_PERIOD = 0.15f; - const float SMILE_PERIOD = 0.925f; - const float SMILE_RANDOM_PERIOD = 0.05f; - - _mouth3 = glm::mix(_audioJawOpen, _mouth3, FUNNEL_PERIOD + randFloat() * FUNNEL_RANDOM_PERIOD); - _mouth2 = glm::mix(_audioJawOpen * MMMM_POWER, _mouth2, MMMM_PERIOD + randFloat() * MMMM_RANDOM_PERIOD); - _mouth4 = glm::mix(_audioJawOpen, _mouth4, SMILE_PERIOD + randFloat() * SMILE_RANDOM_PERIOD); - + calculateMouthShapes(); DependencyManager::get()->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink, _browAudioLift, @@ -230,6 +222,34 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { } +void Head::calculateMouthShapes() { + const float JAW_OPEN_SCALE = 0.015f; + const float JAW_OPEN_RATE = 0.9f; + const float JAW_CLOSE_RATE = 0.90f; + float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE; + if (audioDelta > _audioJawOpen) { + _audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE; + } else { + _audioJawOpen *= JAW_CLOSE_RATE; + } + _audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f); + + // _mouth2 = "mmmm" shape + // _mouth3 = "funnel" shape + // _mouth4 = "smile" shape + const float FUNNEL_PERIOD = 0.985f; + const float FUNNEL_RANDOM_PERIOD = 0.01f; + const float MMMM_POWER = 0.25f; + const float MMMM_PERIOD = 0.91f; + const float MMMM_RANDOM_PERIOD = 0.15f; + const float SMILE_PERIOD = 0.925f; + const float SMILE_RANDOM_PERIOD = 0.05f; + + _mouth3 = glm::mix(_audioJawOpen, _mouth3, FUNNEL_PERIOD + randFloat() * FUNNEL_RANDOM_PERIOD); + _mouth2 = glm::mix(_audioJawOpen * MMMM_POWER, _mouth2, MMMM_PERIOD + randFloat() * MMMM_RANDOM_PERIOD); + _mouth4 = glm::mix(_audioJawOpen, _mouth4, SMILE_PERIOD + randFloat() * SMILE_RANDOM_PERIOD); +} + void Head::relaxLean(float deltaTime) { // restore rotation, lean to neutral positions const float LEAN_RELAXATION_PERIOD = 0.25f; // seconds diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 5465d74172..7ae36675be 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -81,7 +81,7 @@ public: FaceModel& getFaceModel() { return _faceModel; } const FaceModel& getFaceModel() const { return _faceModel; } - const bool getReturnToCenter() const { return _returnHeadToCenter; } // Do you want head to try to return to center (depends on interface detected) + bool getReturnToCenter() const { return _returnHeadToCenter; } // Do you want head to try to return to center (depends on interface detected) float getAverageLoudness() const { return _averageLoudness; } /// \return the point about which scaling occurs. glm::vec3 getScalePivot() const; @@ -154,6 +154,7 @@ private: // private methods void renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition); + void calculateMouthShapes(); friend class FaceModel; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6b70577754..557d630ebf 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "Application.h" #include "AvatarManager.h" @@ -126,7 +127,7 @@ void MyAvatar::reset() { _skeletonModel.reset(); getHead()->reset(); - _velocity = glm::vec3(0.0f); + _targetVelocity = glm::vec3(0.0f); setThrust(glm::vec3(0.0f)); // Reset the pitch and roll components of the avatar's orientation, preserve yaw direction glm::vec3 eulers = safeEulerAngles(getOrientation()); @@ -165,7 +166,6 @@ void MyAvatar::simulate(float deltaTime) { if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); - Application::getInstance()->getCamera()->setScale(scale); } { @@ -605,10 +605,15 @@ void MyAvatar::saveData() { settings.setValue("leanScale", _leanScale); settings.setValue("scale", _targetScale); - - settings.setValue("faceModelURL", _faceModelURL); - settings.setValue("skeletonModelURL", _skeletonModelURL); - + + settings.setValue("useFullAvatar", _useFullAvatar); + settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences); + settings.setValue("faceModelURL", _headURLFromPreferences); + settings.setValue("skeletonModelURL", _skeletonURLFromPreferences); + settings.setValue("headModelName", _headModelName); + settings.setValue("bodyModelName", _bodyModelName); + settings.setValue("fullAvatarModelName", _fullAvatarModelName); + settings.beginWriteArray("attachmentData"); for (int i = 0; i < _attachmentData.size(); i++) { settings.setArrayIndex(i); @@ -640,7 +645,6 @@ void MyAvatar::saveData() { settings.setValue("firstFrame", pointer->getFirstFrame()); settings.setValue("lastFrame", pointer->getLastFrame()); settings.setValue("maskedJoints", pointer->getMaskedJoints()); - settings.setValue("running", pointer->getLoop() && pointer->isRunning()); } settings.endArray(); @@ -668,10 +672,62 @@ void MyAvatar::loadData() { _leanScale = loadSetting(settings, "leanScale", 0.05f); _targetScale = loadSetting(settings, "scale", 1.0f); setScale(_scale); - Application::getInstance()->getCamera()->setScale(_scale); + + // The old preferences only stored the face and skeleton URLs, we didn't track if the user wanted to use 1 or 2 urls + // for their avatar, So we need to attempt to detect this old case and set our new preferences accordingly. If + // the head URL is empty, then we will assume they are using a full url... + bool isOldSettings = !(settings.contains("useFullAvatar") || settings.contains("fullAvatarURL")); + + _useFullAvatar = settings.value("useFullAvatar").toBool(); + _headURLFromPreferences = settings.value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl(); + _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", DEFAULT_FULL_AVATAR_MODEL_URL).toUrl(); + _skeletonURLFromPreferences = settings.value("skeletonModelURL", DEFAULT_BODY_MODEL_URL).toUrl(); + _headModelName = settings.value("headModelName", DEFAULT_HEAD_MODEL_NAME).toString(); + _bodyModelName = settings.value("bodyModelName", DEFAULT_BODY_MODEL_NAME).toString(); + _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); - setFaceModelURL(settings.value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl()); - setSkeletonModelURL(settings.value("skeletonModelURL").toUrl()); + if (isOldSettings) { + bool assumeFullAvatar = _headURLFromPreferences.isEmpty(); + _useFullAvatar = assumeFullAvatar; + + if (_useFullAvatar) { + _fullAvatarURLFromPreferences = settings.value("skeletonModelURL").toUrl(); + _headURLFromPreferences = DEFAULT_HEAD_MODEL_URL; + _skeletonURLFromPreferences = DEFAULT_BODY_MODEL_URL; + + QVariantHash fullAvatarFST = FSTReader::downloadMapping(_fullAvatarURLFromPreferences.toString()); + + _headModelName = "Default"; + _bodyModelName = "Default"; + _fullAvatarModelName = fullAvatarFST["name"].toString(); + + } else { + _fullAvatarURLFromPreferences = DEFAULT_FULL_AVATAR_MODEL_URL; + _skeletonURLFromPreferences = settings.value("skeletonModelURL", DEFAULT_BODY_MODEL_URL).toUrl(); + + if (_skeletonURLFromPreferences == DEFAULT_BODY_MODEL_URL) { + _bodyModelName = DEFAULT_BODY_MODEL_NAME; + } else { + QVariantHash bodyFST = FSTReader::downloadMapping(_skeletonURLFromPreferences.toString()); + _bodyModelName = bodyFST["name"].toString(); + } + + if (_headURLFromPreferences == DEFAULT_HEAD_MODEL_URL) { + _headModelName = DEFAULT_HEAD_MODEL_NAME; + } else { + QVariantHash headFST = FSTReader::downloadMapping(_headURLFromPreferences.toString()); + _headModelName = headFST["name"].toString(); + } + + _fullAvatarModelName = "Default"; + } + } + + if (_useFullAvatar) { + useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); + } else { + useHeadAndBodyURLs(_headURLFromPreferences, _skeletonURLFromPreferences, _headModelName, _bodyModelName); + } QVector attachmentData; int attachmentCount = settings.beginReadArray("attachmentData"); @@ -710,13 +766,10 @@ void MyAvatar::loadData() { handle->setPriority(loadSetting(settings, "priority", 1.0f)); handle->setLoop(settings.value("loop", true).toBool()); handle->setHold(settings.value("hold", false).toBool()); - handle->setStartAutomatically(settings.value("startAutomatically", true).toBool()); handle->setFirstFrame(settings.value("firstFrame", 0.0f).toFloat()); handle->setLastFrame(settings.value("lastFrame", INT_MAX).toFloat()); handle->setMaskedJoints(settings.value("maskedJoints").toStringList()); - if (settings.value("loop", true).toBool() && settings.value("running", false).toBool()) { - handle->setRunning(true); - } + handle->setStartAutomatically(settings.value("startAutomatically", true).toBool()); } settings.endArray(); @@ -897,6 +950,29 @@ void MyAvatar::clearJointAnimationPriorities() { } } +QString MyAvatar::getModelDescription() const { + QString result; + if (_useFullAvatar) { + if (!getFullAvartarModelName().isEmpty()) { + result = "Full Avatar \"" + getFullAvartarModelName() + "\""; + } else { + result = "Full Avatar \"" + _fullAvatarURLFromPreferences.fileName() + "\""; + } + } else { + if (!getHeadModelName().isEmpty()) { + result = "Head \"" + getHeadModelName() + "\""; + } else { + result = "Head \"" + _headURLFromPreferences.fileName() + "\""; + } + if (!getBodyModelName().isEmpty()) { + result += " and Body \"" + getBodyModelName() + "\""; + } else { + result += " and Body \"" + _skeletonURLFromPreferences.fileName() + "\""; + } + } + return result; +} + void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { Avatar::setFaceModelURL(faceModelURL); _billboardValid = false; @@ -907,6 +983,90 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; } +void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection, + Q_ARG(const QUrl&, fullAvatarURL), + Q_ARG(const QString&, modelName)); + return; + } + + _useFullAvatar = true; + + if (_fullAvatarURLFromPreferences != fullAvatarURL) { + _fullAvatarURLFromPreferences = fullAvatarURL; + if (modelName.isEmpty()) { + QVariantHash fullAvatarFST = FSTReader::downloadMapping(_fullAvatarURLFromPreferences.toString()); + _fullAvatarModelName = fullAvatarFST["name"].toString(); + } else { + _fullAvatarModelName = modelName; + } + } + + if (!getFaceModelURLString().isEmpty()) { + setFaceModelURL(QString()); + } + + if (fullAvatarURL != getSkeletonModelURL()) { + setSkeletonModelURL(fullAvatarURL); + UserActivityLogger::getInstance().changedModel("skeleton", fullAvatarURL.toString()); + } + sendIdentityPacket(); +} + +void MyAvatar::useHeadURL(const QUrl& headURL, const QString& modelName) { + useHeadAndBodyURLs(headURL, _skeletonURLFromPreferences, modelName, _bodyModelName); +} + +void MyAvatar::useBodyURL(const QUrl& bodyURL, const QString& modelName) { + useHeadAndBodyURLs(_headURLFromPreferences, bodyURL, _headModelName, modelName); +} + +void MyAvatar::useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName, const QString& bodyName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection, + Q_ARG(const QUrl&, headURL), + Q_ARG(const QUrl&, bodyURL), + Q_ARG(const QString&, headName), + Q_ARG(const QString&, bodyName)); + return; + } + + _useFullAvatar = false; + + if (_headURLFromPreferences != headURL) { + _headURLFromPreferences = headURL; + if (headName.isEmpty()) { + QVariantHash headFST = FSTReader::downloadMapping(_headURLFromPreferences.toString()); + _headModelName = headFST["name"].toString(); + } else { + _headModelName = headName; + } + } + + if (_skeletonURLFromPreferences != bodyURL) { + _skeletonURLFromPreferences = bodyURL; + if (bodyName.isEmpty()) { + QVariantHash bodyFST = FSTReader::downloadMapping(_skeletonURLFromPreferences.toString()); + _bodyModelName = bodyFST["name"].toString(); + } else { + _bodyModelName = bodyName; + } + } + + if (headURL != getFaceModelURL()) { + setFaceModelURL(headURL); + UserActivityLogger::getInstance().changedModel("head", headURL.toString()); + } + + if (bodyURL != getSkeletonModelURL()) { + setSkeletonModelURL(bodyURL); + UserActivityLogger::getInstance().changedModel("skeleton", bodyURL.toString()); + } + sendIdentityPacket(); +} + + void MyAvatar::setAttachmentData(const QVector& attachmentData) { Avatar::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { @@ -932,7 +1092,7 @@ glm::vec3 MyAvatar::getSkeletonPosition() const { return Avatar::getPosition(); } -void MyAvatar::updateCharacterController() { +void MyAvatar::rebuildSkeletonBody() { // compute localAABox const CapsuleShape& capsule = _skeletonModel.getBoundingShape(); float radius = capsule.getRadius(); @@ -1240,28 +1400,28 @@ glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVe void MyAvatar::updatePosition(float deltaTime) { // rotate velocity into camera frame glm::quat rotation = getHead()->getCameraOrientation(); - glm::vec3 localVelocity = glm::inverse(rotation) * _velocity; + glm::vec3 localVelocity = glm::inverse(rotation) * _targetVelocity; bool isHovering = _characterController.isHovering(); glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isHovering); newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); // rotate back into world-frame - _velocity = rotation * newLocalVelocity; + _targetVelocity = rotation * newLocalVelocity; - _velocity += _thrust * deltaTime; + _targetVelocity += _thrust * deltaTime; _thrust = glm::vec3(0.0f); // cap avatar speed - float speed = glm::length(_velocity); + float speed = glm::length(_targetVelocity); if (speed > MAX_AVATAR_SPEED) { - _velocity *= MAX_AVATAR_SPEED / speed; + _targetVelocity *= MAX_AVATAR_SPEED / speed; speed = MAX_AVATAR_SPEED; } if (speed > MIN_AVATAR_SPEED && !_characterController.isEnabled()) { // update position ourselves - applyPositionDelta(deltaTime * _velocity); + applyPositionDelta(deltaTime * _targetVelocity); measureMotionDerivatives(deltaTime); } // else physics will move avatar later diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 8abae712ad..4746a40099 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -116,14 +116,28 @@ public: virtual void setJointData(int index, const glm::quat& rotation); virtual void clearJointData(int index); virtual void clearJointsData(); - virtual void setFaceModelURL(const QUrl& faceModelURL); - virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + + Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); + Q_INVOKABLE void useHeadURL(const QUrl& headURL, const QString& modelName = QString()); + Q_INVOKABLE void useBodyURL(const QUrl& bodyURL, const QString& modelName = QString()); + Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName = QString(), const QString& bodyName = QString()); + + Q_INVOKABLE bool getUseFullAvatar() const { return _useFullAvatar; } + Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } + Q_INVOKABLE const QUrl& getHeadURLFromPreferences() const { return _headURLFromPreferences; } + Q_INVOKABLE const QUrl& getBodyURLFromPreferences() const { return _skeletonURLFromPreferences; } + + Q_INVOKABLE const QString& getHeadModelName() const { return _headModelName; } + Q_INVOKABLE const QString& getBodyModelName() const { return _bodyModelName; } + Q_INVOKABLE const QString& getFullAvartarModelName() const { return _fullAvatarModelName; } + + Q_INVOKABLE QString getModelDescription() const; + virtual void setAttachmentData(const QVector& attachmentData); virtual glm::vec3 getSkeletonPosition() const; void updateLocalAABox(); DynamicCharacterController* getCharacterController() { return &_characterController; } - void updateCharacterController(); void clearJointAnimationPriorities(); @@ -177,6 +191,8 @@ public slots: void stopRecording(); void saveRecording(QString filename); void loadLastRecording(); + + virtual void rebuildSkeletonBody(); signals: void transformChanged(); @@ -185,6 +201,11 @@ protected: virtual void renderAttachments(RenderArgs::RenderMode renderMode, RenderArgs* args); private: + + // These are made private for MyAvatar so that you will use the "use" methods instead + virtual void setFaceModelURL(const QUrl& faceModelURL); + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + float _turningKeyPressTime; glm::vec3 _gravity; @@ -229,6 +250,16 @@ private: void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void maybeUpdateBillboard(); + + // Avatar Preferences + bool _useFullAvatar = false; + QUrl _fullAvatarURLFromPreferences; + QUrl _headURLFromPreferences; + QUrl _skeletonURLFromPreferences; + + QString _headModelName; + QString _bodyModelName; + QString _fullAvatarModelName; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index dc5427ab48..8c52c79ca0 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -40,13 +40,14 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : _clampedFootPosition(0.0f), _headClipDistance(DEFAULT_NEAR_CLIP) { + assert(_owningAvatar); } SkeletonModel::~SkeletonModel() { } -void SkeletonModel::setJointStates(QVector states) { - Model::setJointStates(states); +void SkeletonModel::initJointStates(QVector states) { + Model::initJointStates(states); // Determine the default eye position for avatar scale = 1.0 int headJointIndex = _geometry->getFBXGeometry().headJointIndex; @@ -83,6 +84,7 @@ void SkeletonModel::setJointStates(QVector states) { _headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z); _headClipDistance = std::max(_headClipDistance, DEFAULT_NEAR_CLIP); + _owningAvatar->rebuildSkeletonBody(); emit skeletonLoaded(); } diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 298d74fb7a..e28988326a 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -28,7 +28,7 @@ public: SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL); ~SkeletonModel(); - void setJointStates(QVector states); + virtual void initJointStates(QVector states); void simulate(float deltaTime, bool fullUpdate = true); diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 751739ed24..b6525ffb52 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -11,18 +11,32 @@ #include +#include #include #include #include -#include +#include + +#include #include "DdeFaceTracker.h" #include "FaceshiftConstants.h" #include "InterfaceLogging.h" +#include "Menu.h" -static const QHostAddress DDE_FEATURE_POINT_SERVER_ADDR("127.0.0.1"); -static const quint16 DDE_FEATURE_POINT_SERVER_PORT = 5555; +static const QHostAddress DDE_SERVER_ADDR("127.0.0.1"); +static const quint16 DDE_SERVER_PORT = 64204; +static const quint16 DDE_CONTROL_PORT = 64205; +#if defined(Q_OS_WIN) +static const QString DDE_PROGRAM_PATH = "/dde/dde.exe"; +#elif defined(Q_OS_MAC) +static const QString DDE_PROGRAM_PATH = "/dde.app/Contents/MacOS/dde"; +#endif +static const QStringList DDE_ARGUMENTS = QStringList() + << "--udp=" + DDE_SERVER_ADDR.toString() + ":" + QString::number(DDE_SERVER_PORT) + << "--receiver=" + QString::number(DDE_CONTROL_PORT) + << "--headless"; static const int NUM_EXPRESSIONS = 46; static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSIONS) * sizeof(float) + sizeof(int); @@ -51,16 +65,16 @@ static const int DDE_TO_FACESHIFT_MAPPING[] = { // The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much // less than this. static const float DDE_COEFFICIENT_SCALES[] = { - 4.0f, // EyeBlink_L - 4.0f, // EyeBlink_R + 1.0f, // EyeBlink_L + 1.0f, // EyeBlink_R 1.0f, // EyeSquint_L 1.0f, // EyeSquint_R 1.0f, // EyeDown_L 1.0f, // EyeDown_R 1.0f, // EyeIn_L 1.0f, // EyeIn_R - 4.0f, // EyeOpen_L - 4.0f, // EyeOpen_R + 1.0f, // EyeOpen_L + 1.0f, // EyeOpen_R 1.0f, // EyeOut_L 1.0f, // EyeOut_R 1.0f, // EyeUp_L @@ -120,15 +134,30 @@ struct Packet { char name[MAX_NAME_SIZE + 1]; }; +const float STARTING_DDE_MESSAGE_TIME = 0.033f; + +const int FPS_TIMER_DELAY = 2000; // ms +const int FPS_TIMER_DURATION = 2000; // ms + +#ifdef WIN32 +// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeBlinks' will be default initialized +// warning C4351: new behavior: elements of array 'DdeFaceTracker::_filteredEyeBlinks' will be default initialized +// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeCoefficients' will be default initialized +#pragma warning(disable:4351) +#endif + DdeFaceTracker::DdeFaceTracker() : - DdeFaceTracker(QHostAddress::Any, DDE_FEATURE_POINT_SERVER_PORT) + DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT) { } -DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) : +DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort) : + _ddeProcess(NULL), + _ddeStopping(false), _host(host), - _port(port), + _serverPort(serverPort), + _controlPort(controlPort), _lastReceiveTimestamp(0), _reset(false), _leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes @@ -143,30 +172,95 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) : _mouthSmileLeftIndex(28), _mouthSmileRightIndex(29), _jawOpenIndex(21), - _previousTranslation(glm::vec3()), - _previousRotation(glm::quat()) + _lastMessageReceived(0), + _averageMessageTime(STARTING_DDE_MESSAGE_TIME), + _lastHeadTranslation(glm::vec3(0.0f)), + _filteredHeadTranslation(glm::vec3(0.0f)), + _lastBrowUp(0.0f), + _filteredBrowUp(0.0f), + _lastEyeBlinks(), + _filteredEyeBlinks(), + _lastEyeCoefficients(), + _isCalculatingFPS(false), + _frameCount(0) { _coefficients.resize(NUM_FACESHIFT_BLENDSHAPES); - _previousCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES); _blendshapeCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES); - + + _eyeStates[0] = EYE_OPEN; + _eyeStates[1] = EYE_OPEN; + connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState))); + connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + SLOT(socketStateChanged(QAbstractSocket::SocketState))); } DdeFaceTracker::~DdeFaceTracker() { - if (_udpSocket.isOpen()) { - _udpSocket.close(); - } + setEnabled(false); } +#ifdef WIN32 +#pragma warning(default:4351) +#endif + void DdeFaceTracker::setEnabled(bool enabled) { +#ifdef HAVE_DDE // isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket. _udpSocket.close(); if (enabled) { - _udpSocket.bind(_host, _port); + _udpSocket.bind(_host, _serverPort); + } + + const char* DDE_EXIT_COMMAND = "exit"; + + if (enabled && !_ddeProcess) { + // Terminate any existing DDE process, perhaps left running after an Interface crash + _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); + _ddeStopping = false; + + qCDebug(interfaceapp) << "DDE Face Tracker: Starting"; + _ddeProcess = new QProcess(qApp); + connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); + _ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS); + } + + if (!enabled && _ddeProcess) { + _ddeStopping = true; + _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); + qCDebug(interfaceapp) << "DDE Face Tracker: Stopping"; + } +#endif +} + +void DdeFaceTracker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { + if (_ddeProcess) { + if (_ddeStopping) { + qCDebug(interfaceapp) << "DDE Face Tracker: Stopped"; + + } else { + qCWarning(interfaceapp) << "DDE Face Tracker: Stopped unexpectedly"; + Menu::getInstance()->setIsOptionChecked(MenuOption::NoFaceTracking, true); + } + _udpSocket.close(); + delete _ddeProcess; + _ddeProcess = NULL; + } +} + +void DdeFaceTracker::reset() { + if (_udpSocket.state() == QAbstractSocket::BoundState) { + _reset = true; + + qCDebug(interfaceapp) << "DDE Face Tracker: Reset"; + + const char* DDE_RESET_COMMAND = "reset"; + _udpSocket.writeDatagram(DDE_RESET_COMMAND, DDE_SERVER_ADDR, _controlPort); + + FaceTracker::reset(); + + _reset = true; } } @@ -177,7 +271,7 @@ bool DdeFaceTracker::isActive() const { //private slots and methods void DdeFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) { - qCDebug(interfaceapp) << "[Error] DDE Face Tracker Socket Error: " << _udpSocket.errorString(); + qCWarning(interfaceapp) << "DDE Face Tracker: Socket error: " << _udpSocket.errorString(); } void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) { @@ -205,7 +299,7 @@ void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState state = "Unconnected"; break; } - qCDebug(interfaceapp) << "[Info] DDE Face Tracker Socket: " << state; + qCDebug(interfaceapp) << "DDE Face Tracker: Socket: " << state; } void DdeFaceTracker::readPendingDatagrams() { @@ -223,6 +317,8 @@ float DdeFaceTracker::getBlendshapeCoefficient(int index) const { void DdeFaceTracker::decodePacket(const QByteArray& buffer) { if(buffer.size() > MIN_PACKET_SIZE) { + bool isFiltering = Menu::getInstance()->isOptionChecked(MenuOption::VelocityFilter); + Packet packet; int bytesToCopy = glm::min((int)sizeof(packet), buffer.size()); memset(&packet.name, '\n', MAX_NAME_SIZE + 1); @@ -239,47 +335,61 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { } // Compute relative translation - float LEAN_DAMPING_FACTOR = 200.0f; + float LEAN_DAMPING_FACTOR = 75.0f; translation -= _referenceTranslation; translation /= LEAN_DAMPING_FACTOR; translation.x *= -1; - _headTranslation = (translation + _previousTranslation) / 2.0f; - _previousTranslation = translation; + if (isFiltering) { + glm::vec3 linearVelocity = (translation - _lastHeadTranslation) / _averageMessageTime; + const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f; + float velocityFilter = glm::clamp(1.0f - glm::length(linearVelocity) * + LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); + _filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * translation; + _lastHeadTranslation = translation; + _headTranslation = _filteredHeadTranslation; + } else { + _headTranslation = translation; + } // Compute relative rotation rotation = glm::inverse(_referenceRotation) * rotation; - _headRotation = (rotation + _previousRotation) / 2.0f; - _previousRotation = rotation; + if (isFiltering) { + glm::quat r = rotation * glm::inverse(_headRotation); + float theta = 2 * acos(r.w); + glm::vec3 angularVelocity; + if (theta > EPSILON) { + float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); + angularVelocity = theta / _averageMessageTime * glm::vec3(r.x, r.y, r.z) / rMag; + } else { + angularVelocity = glm::vec3(0, 0, 0); + } + const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f; + _headRotation = safeMix(_headRotation, rotation, glm::clamp(glm::length(angularVelocity) * + ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f)); + } else { + _headRotation = rotation; + } // Translate DDE coefficients to Faceshift compatible coefficients - for (int i = 0; i < NUM_EXPRESSIONS; i += 1) { + for (int i = 0; i < NUM_EXPRESSIONS; i++) { _coefficients[DDE_TO_FACESHIFT_MAPPING[i]] = packet.expressions[i]; } - // Use EyeBlink values to control both EyeBlink and EyeOpen - static const float RELAXED_EYE_VALUE = 0.1f; - float leftEye = (_coefficients[_leftBlinkIndex] + _previousCoefficients[_leftBlinkIndex]) / 2.0f; - float rightEye = (_coefficients[_rightBlinkIndex] + _previousCoefficients[_rightBlinkIndex]) / 2.0f; - if (leftEye > RELAXED_EYE_VALUE) { - _coefficients[_leftBlinkIndex] = leftEye - RELAXED_EYE_VALUE; - _coefficients[_leftEyeOpenIndex] = 0.0f; - } else { - _coefficients[_leftBlinkIndex] = 0.0f; - _coefficients[_leftEyeOpenIndex] = RELAXED_EYE_VALUE - leftEye; - } - if (rightEye > RELAXED_EYE_VALUE) { - _coefficients[_rightBlinkIndex] = rightEye - RELAXED_EYE_VALUE; - _coefficients[_rightEyeOpenIndex] = 0.0f; - } else { - _coefficients[_rightBlinkIndex] = 0.0f; - _coefficients[_rightEyeOpenIndex] = RELAXED_EYE_VALUE - rightEye; - } - // Use BrowsU_C to control both brows' up and down - _coefficients[_browDownLeftIndex] = -_coefficients[_browUpCenterIndex]; - _coefficients[_browDownRightIndex] = -_coefficients[_browUpCenterIndex]; - _coefficients[_browUpLeftIndex] = _coefficients[_browUpCenterIndex]; - _coefficients[_browUpRightIndex] = _coefficients[_browUpCenterIndex]; + float browUp = _coefficients[_browUpCenterIndex]; + if (isFiltering) { + const float BROW_VELOCITY_FILTER_STRENGTH = 0.75f; + float velocity = fabs(browUp - _lastBrowUp) / _averageMessageTime; + float velocityFilter = glm::clamp(velocity * BROW_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); + _filteredBrowUp = velocityFilter * browUp + (1.0f - velocityFilter) * _filteredBrowUp; + _lastBrowUp = browUp; + browUp = _filteredBrowUp; + _coefficients[_browUpCenterIndex] = browUp; + } + _coefficients[_browUpLeftIndex] = browUp; + _coefficients[_browUpRightIndex] = browUp; + _coefficients[_browDownLeftIndex] = -browUp; + _coefficients[_browDownRightIndex] = -browUp; // Offset jaw open coefficient static const float JAW_OPEN_THRESHOLD = 0.16f; @@ -290,16 +400,108 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { _coefficients[_mouthSmileLeftIndex] = _coefficients[_mouthSmileLeftIndex] - SMILE_THRESHOLD; _coefficients[_mouthSmileRightIndex] = _coefficients[_mouthSmileRightIndex] - SMILE_THRESHOLD; - - // Scale all coefficients - for (int i = 0; i < NUM_EXPRESSIONS; i += 1) { - _blendshapeCoefficients[i] - = glm::clamp(DDE_COEFFICIENT_SCALES[i] * (_coefficients[i] + _previousCoefficients[i]) / 2.0f, 0.0f, 1.0f); - _previousCoefficients[i] = _coefficients[i]; + // Velocity filter EyeBlink values + const float DDE_EYEBLINK_SCALE = 3.0f; + float eyeBlinks[] = { DDE_EYEBLINK_SCALE * _coefficients[_leftBlinkIndex], DDE_EYEBLINK_SCALE * _coefficients[_rightBlinkIndex] }; + if (isFiltering) { + const float BLINK_VELOCITY_FILTER_STRENGTH = 0.3f; + for (int i = 0; i < 2; i++) { + float velocity = fabs(eyeBlinks[i] - _lastEyeBlinks[i]) / _averageMessageTime; + float velocityFilter = glm::clamp(velocity * BLINK_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); + _filteredEyeBlinks[i] = velocityFilter * eyeBlinks[i] + (1.0f - velocityFilter) * _filteredEyeBlinks[i]; + _lastEyeBlinks[i] = eyeBlinks[i]; + } } + // Finesse EyeBlink values + float eyeCoefficients[2]; + for (int i = 0; i < 2; i++) { + // Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen + // -ve values control EyeOpen; +ve values control EyeBlink + static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value + eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD); + + // Change to closing or opening states + const float EYE_CONTROL_HYSTERISIS = 0.25f; + const float EYE_CLOSING_THRESHOLD = 0.95f; + const float EYE_OPENING_THRESHOLD = EYE_CONTROL_THRESHOLD - EYE_CONTROL_HYSTERISIS; + if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > EYE_CLOSING_THRESHOLD) { + _eyeStates[i] = EYE_CLOSING; + } else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING) + && eyeCoefficients[i] < EYE_OPENING_THRESHOLD) { + _eyeStates[i] = EYE_OPENING; + } + + const float EYELID_MOVEMENT_RATE = 10.0f; // units/second + const float EYE_OPEN_SCALE = 0.2f; + if (_eyeStates[i] == EYE_CLOSING) { + // Close eyelid until it's fully closed + float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime; + if (closingValue >= 1.0) { + _eyeStates[i] = EYE_CLOSED; + eyeCoefficients[i] = 1.0; + } else { + eyeCoefficients[i] = closingValue; + } + } else if (_eyeStates[i] == EYE_OPENING) { + // Open eyelid until it meets the current adjusted value + float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime; + if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) { + _eyeStates[i] = EYE_OPEN; + eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE; + } else { + eyeCoefficients[i] = openingValue; + } + } else if (_eyeStates[i] == EYE_OPEN) { + // Reduce eyelid movement + eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE; + } else if (_eyeStates[i] == EYE_CLOSED) { + // Keep eyelid fully closed + eyeCoefficients[i] = 1.0; + } + } + if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) { + // Couple eyelids + eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f; + } + _lastEyeCoefficients[0] = eyeCoefficients[0]; + _lastEyeCoefficients[1] = eyeCoefficients[1]; + + // Use EyeBlink values to control both EyeBlink and EyeOpen + if (eyeCoefficients[0] > 0) { + _coefficients[_leftBlinkIndex] = eyeCoefficients[0]; + _coefficients[_leftEyeOpenIndex] = 0.0f; + } else { + _coefficients[_leftBlinkIndex] = 0.0f; + _coefficients[_leftEyeOpenIndex] = -eyeCoefficients[0]; + } + if (eyeCoefficients[1] > 0) { + _coefficients[_rightBlinkIndex] = eyeCoefficients[1]; + _coefficients[_rightEyeOpenIndex] = 0.0f; + } else { + _coefficients[_rightBlinkIndex] = 0.0f; + _coefficients[_rightEyeOpenIndex] = -eyeCoefficients[1]; + } + + // Scale all coefficients + for (int i = 0; i < NUM_EXPRESSIONS; i++) { + _blendshapeCoefficients[i] + = glm::clamp(DDE_COEFFICIENT_SCALES[i] * _coefficients[i], 0.0f, 1.0f); + } + + // Calculate average frame time + const float FRAME_AVERAGING_FACTOR = 0.99f; + quint64 usecsNow = usecTimestampNow(); + if (_lastMessageReceived != 0) { + _averageMessageTime = FRAME_AVERAGING_FACTOR * _averageMessageTime + + (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f; + } + _lastMessageReceived = usecsNow; + + FaceTracker::countFrame(); + } else { - qCDebug(interfaceapp) << "[Error] DDE Face Tracker Decode Error"; + qCWarning(interfaceapp) << "DDE Face Tracker: Decode error"; } _lastReceiveTimestamp = usecTimestampNow(); } diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index 3c95666a6c..9fb0e943f2 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -12,6 +12,11 @@ #ifndef hifi_DdeFaceTracker_h #define hifi_DdeFaceTracker_h +#if defined(Q_OS_WIN) || defined(Q_OS_OSX) + #define HAVE_DDE +#endif + +#include #include #include @@ -23,7 +28,7 @@ class DdeFaceTracker : public FaceTracker, public Dependency { SINGLETON_DEPENDENCY public: - virtual void reset() { _reset = true; } + virtual void reset(); virtual bool isActive() const; virtual bool isTracking() const { return isActive(); } @@ -47,19 +52,24 @@ public slots: void setEnabled(bool enabled); private slots: - + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + //sockets void socketErrorOccurred(QAbstractSocket::SocketError socketError); void readPendingDatagrams(); void socketStateChanged(QAbstractSocket::SocketState socketState); - + private: DdeFaceTracker(); - DdeFaceTracker(const QHostAddress& host, quint16 port); - ~DdeFaceTracker(); + DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort); + virtual ~DdeFaceTracker(); + + QProcess* _ddeProcess; + bool _ddeStopping; QHostAddress _host; - quint16 _port; + quint16 _serverPort; + quint16 _controlPort; float getBlendshapeCoefficient(int index) const; void decodePacket(const QByteArray& buffer); @@ -91,10 +101,28 @@ private: QVector _coefficients; - // Previous values for simple smoothing - glm::vec3 _previousTranslation; - glm::quat _previousRotation; - QVector _previousCoefficients; + quint64 _lastMessageReceived; + float _averageMessageTime; + + glm::vec3 _lastHeadTranslation; + glm::vec3 _filteredHeadTranslation; + + float _lastBrowUp; + float _filteredBrowUp; + + enum EyeState { + EYE_OPEN, + EYE_CLOSING, + EYE_CLOSED, + EYE_OPENING + }; + EyeState _eyeStates[2]; + float _lastEyeBlinks[2]; + float _filteredEyeBlinks[2]; + float _lastEyeCoefficients[2]; + + bool _isCalculatingFPS; + int _frameCount; }; -#endif // hifi_DdeFaceTracker_h \ No newline at end of file +#endif // hifi_DdeFaceTracker_h diff --git a/interface/src/devices/FaceTracker.cpp b/interface/src/devices/FaceTracker.cpp index 7c1c757ec1..0d40249c26 100644 --- a/interface/src/devices/FaceTracker.cpp +++ b/interface/src/devices/FaceTracker.cpp @@ -9,9 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include "FaceTracker.h" +#include "InterfaceLogging.h" + +const int FPS_TIMER_DELAY = 2000; // ms +const int FPS_TIMER_DURATION = 2000; // ms + +FaceTracker::FaceTracker() : + _isCalculatingFPS(false), + _frameCount(0) +{ +} inline float FaceTracker::getBlendshapeCoefficient(int index) const { return isValidBlendshapeIndex(index) ? glm::mix(0.0f, _blendshapeCoefficients[index], getFadeCoefficient()) @@ -65,4 +77,27 @@ void FaceTracker::update(float deltaTime) { _relaxationStatus = glm::clamp(_relaxationStatus - deltaTime / RELAXATION_TIME, 0.0f, 1.0f); _fadeCoefficient = std::exp(-(1.0f - _relaxationStatus) * INVERSE_AT_EPSILON); } -} \ No newline at end of file +} + +void FaceTracker::reset() { + if (isActive() && !_isCalculatingFPS) { + QTimer::singleShot(FPS_TIMER_DELAY, this, SLOT(startFPSTimer())); + _isCalculatingFPS = true; + } +} + +void FaceTracker::startFPSTimer() { + _frameCount = 0; + QTimer::singleShot(FPS_TIMER_DURATION, this, SLOT(finishFPSTimer())); +} + +void FaceTracker::countFrame() { + if (_isCalculatingFPS) { + _frameCount++; + } +} + +void FaceTracker::finishFPSTimer() { + qCDebug(interfaceapp) << "Face tracker FPS =" << (float)_frameCount / ((float)FPS_TIMER_DURATION / 1000.0f); + _isCalculatingFPS = false; +} diff --git a/interface/src/devices/FaceTracker.h b/interface/src/devices/FaceTracker.h index 362099aaf6..a0a434ee9e 100644 --- a/interface/src/devices/FaceTracker.h +++ b/interface/src/devices/FaceTracker.h @@ -18,7 +18,7 @@ #include #include -/// Base class for face trackers (Faceshift, Visage, DDE). +/// Base class for face trackers (Faceshift, DDE). class FaceTracker : public QObject { Q_OBJECT @@ -28,7 +28,7 @@ public: virtual void init() {} virtual void update(float deltaTime); - virtual void reset() {} + virtual void reset(); float getFadeCoefficient() const; @@ -44,6 +44,9 @@ public: float getBlendshapeCoefficient(int index) const; protected: + FaceTracker(); + virtual ~FaceTracker() {}; + glm::vec3 _headTranslation = glm::vec3(0.0f); glm::quat _headRotation = glm::quat(); float _estimatedEyePitch = 0.0f; @@ -52,6 +55,16 @@ protected: float _relaxationStatus = 0.0f; // Between 0.0f and 1.0f float _fadeCoefficient = 0.0f; // Between 0.0f and 1.0f + + void countFrame(); + +private slots: + void startFPSTimer(); + void finishFPSTimer(); + +private: + bool _isCalculatingFPS; + int _frameCount; }; #endif // hifi_FaceTracker_h diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 4768571bd2..8a0e5a324c 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -39,6 +39,7 @@ Faceshift::Faceshift() : connect(&_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); connect(&_tcpSocket, SIGNAL(readyRead()), SLOT(readFromSocket())); connect(&_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(connectionStateChanged())); + connect(&_tcpSocket, SIGNAL(disconnected()), SLOT(noteDisconnected())); connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); @@ -78,6 +79,10 @@ void Faceshift::update(float deltaTime) { void Faceshift::reset() { if (_tcpSocket.state() == QAbstractSocket::ConnectedState) { + qCDebug(interfaceapp, "Faceshift: Reset"); + + FaceTracker::reset(); + string message; fsBinaryStream::encode_message(message, fsMsgCalibrateNeutral()); send(message); @@ -127,6 +132,7 @@ void Faceshift::setTCPEnabled(bool enabled) { if ((_tcpEnabled = enabled)) { connectSocket(); } else { + qCDebug(interfaceapp, "Faceshift: Disconnecting..."); _tcpSocket.disconnectFromHost(); } #endif @@ -145,7 +151,7 @@ void Faceshift::connectSocket() { void Faceshift::noteConnected() { #ifdef HAVE_FACESHIFT - qCDebug(interfaceapp, "Faceshift: Connected."); + qCDebug(interfaceapp, "Faceshift: Connected"); // request the list of blendshape names string message; fsBinaryStream::encode_message(message, fsMsgSendBlendshapeNames()); @@ -153,10 +159,16 @@ void Faceshift::noteConnected() { #endif } +void Faceshift::noteDisconnected() { +#ifdef HAVE_FACESHIFT + qCDebug(interfaceapp, "Faceshift: Disconnected"); +#endif +} + void Faceshift::noteError(QAbstractSocket::SocketError error) { if (!_tcpRetryCount) { // Only spam log with fail to connect the first time, so that we can keep waiting for server - qCDebug(interfaceapp) << "Faceshift: " << _tcpSocket.errorString(); + qCWarning(interfaceapp) << "Faceshift: " << _tcpSocket.errorString(); } // retry connection after a 2 second delay if (_tcpEnabled) { @@ -283,6 +295,8 @@ void Faceshift::receive(const QByteArray& buffer) { } } #endif + + FaceTracker::countFrame(); } void Faceshift::setEyeDeflection(float faceshiftEyeDeflection) { @@ -292,3 +306,4 @@ void Faceshift::setEyeDeflection(float faceshiftEyeDeflection) { void Faceshift::setHostname(const QString& hostname) { _hostname.set(hostname); } + diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index f224448b8e..3d38af5654 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -95,7 +95,8 @@ private slots: void noteError(QAbstractSocket::SocketError error); void readPendingDatagrams(); void readFromSocket(); - + void noteDisconnected(); + private: Faceshift(); virtual ~Faceshift() {} diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index d56ece12fb..7d719873f4 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include @@ -35,6 +34,8 @@ #include "InterfaceLogging.h" #include "Application.h" +#include + template void for_each_eye(Function function) { for (ovrEyeType eye = ovrEyeType::ovrEye_Left; @@ -114,8 +115,11 @@ void OculusManager::initSdk() { } void OculusManager::shutdownSdk() { - ovrHmd_Destroy(_ovrHmd); - ovr_Shutdown(); + if (_ovrHmd) { + ovrHmd_Destroy(_ovrHmd); + _ovrHmd = nullptr; + ovr_Shutdown(); + } } void OculusManager::init() { @@ -124,6 +128,12 @@ void OculusManager::init() { #endif } +void OculusManager::deinit() { +#ifdef OVR_DIRECT_MODE + shutdownSdk(); +#endif +} + void OculusManager::connect() { #ifndef OVR_DIRECT_MODE initSdk(); @@ -515,13 +525,14 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p // We only need to render the overlays to a texture once, then we just render the texture on the hemisphere // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() - applicationOverlay.renderOverlay(true); + applicationOverlay.renderOverlay(); //Bind our framebuffer object. If we are rendering the glow effect, we let the glow effect shader take care of it if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) { DependencyManager::get()->prepare(); } else { - DependencyManager::get()->getPrimaryFramebufferObject()->bind(); + auto primaryFBO = DependencyManager::get()->getPrimaryFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFBO)); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } @@ -612,15 +623,15 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p glPopMatrix(); - QOpenGLFramebufferObject * finalFbo = nullptr; + gpu::FramebufferPointer finalFbo; //Bind the output texture from the glow shader. If glow effect is disabled, we just grab the texture if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) { //Full texture viewport for glow effect glViewport(0, 0, _renderTargetSize.w, _renderTargetSize.h); finalFbo = DependencyManager::get()->render(true); } else { - finalFbo = DependencyManager::get()->getPrimaryFramebufferObject(); - finalFbo->release(); + finalFbo = DependencyManager::get()->getPrimaryFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); } glMatrixMode(GL_PROJECTION); @@ -644,7 +655,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p //Left over from when OR was not connected. glClear(GL_COLOR_BUFFER_BIT); - glBindTexture(GL_TEXTURE_2D, finalFbo->texture()); + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(finalFbo->getRenderBuffer(0))); //Renders the distorted mesh onto the screen renderDistortionMesh(eyeRenderPose); diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index fe2da31231..4b1bc98fb2 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -51,6 +51,7 @@ class Text3DOverlay; class OculusManager { public: static void init(); + static void deinit(); static void connect(); static void disconnect(); static bool isConnected(); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index f082c6de47..05d89d5a6d 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -11,8 +11,6 @@ #include "InterfaceConfig.h" -#include - #include #include @@ -103,7 +101,7 @@ void TV3DManager::display(Camera& whichCamera) { // We only need to render the overlays to a texture once, then we just render the texture as a quad // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() - applicationOverlay.renderOverlay(true); + applicationOverlay.renderOverlay(); DependencyManager::get()->prepare(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp deleted file mode 100644 index 010b872bc6..0000000000 --- a/interface/src/devices/Visage.cpp +++ /dev/null @@ -1,191 +0,0 @@ -// -// Visage.cpp -// interface/src/devices -// -// Created by Andrzej Kapolka on 2/11/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - - -#include -#include -#include -#include -#include - -#include "Application.h" -#include "Faceshift.h" -#include "Visage.h" - -// this has to go after our normal includes, because its definition of HANDLE conflicts with Qt's -#ifdef HAVE_VISAGE -#include -#endif - -namespace VisageSDK { -#ifdef WIN32 - void __declspec(dllimport) initializeLicenseManager(char* licenseKeyFileName); -#else - void initializeLicenseManager(char* licenseKeyFileName); -#endif -} - -using namespace VisageSDK; - -const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.7f); - -Visage::Visage() : - _enabled(false), - _headOrigin(DEFAULT_HEAD_ORIGIN) { - -#ifdef HAVE_VISAGE -#ifdef WIN32 - QByteArray licensePath = PathUtils::resourcesPath().toLatin1() + "visage"; -#else - QByteArray licensePath = PathUtils::resourcesPath().toLatin1() + "visage/license.vlc"; -#endif - initializeLicenseManager(licensePath.data()); - _tracker = new VisageTracker2(PathUtils::resourcesPath().toLatin1() + "visage/tracker.cfg"); - _data = new FaceData(); -#endif -} - -Visage::~Visage() { -#ifdef HAVE_VISAGE - _tracker->stop(); - // deleting the tracker crashes windows; disable for now - //delete _tracker; - delete _data; -#endif -} - -#ifdef HAVE_VISAGE -static int leftEyeBlinkIndex = 0; -static int rightEyeBlinkIndex = 1; - -static QMultiHash > createActionUnitNameMap() { - QMultiHash > blendshapeMap; - blendshapeMap.insert("JawFwd", QPair("au_jaw_z_push", 1.0f)); - blendshapeMap.insert("JawLeft", QPair("au_jaw_x_push", 1.0f)); - blendshapeMap.insert("JawOpen", QPair("au_jaw_drop", 1.0f)); - blendshapeMap.insert("LipsLowerDown", QPair("au_lower_lip_drop", 1.0f)); - blendshapeMap.insert("LipsUpperOpen", QPair("au_upper_lip_raiser", 1.0f)); - blendshapeMap.insert("LipsStretch_R", QPair("au_lip_stretcher_left", 0.5f)); - blendshapeMap.insert("MouthSmile_L", QPair("au_lip_corner_depressor", -1.0f)); - blendshapeMap.insert("MouthSmile_R", QPair("au_lip_corner_depressor", -1.0f)); - blendshapeMap.insert("BrowsU_R", QPair("au_left_outer_brow_raiser", 1.0f)); - blendshapeMap.insert("BrowsU_C", QPair("au_left_inner_brow_raiser", 1.0f)); - blendshapeMap.insert("BrowsD_R", QPair("au_left_brow_lowerer", 1.0f)); - blendshapeMap.insert("EyeBlink_L", QPair("au_leye_closed", 1.0f)); - blendshapeMap.insert("EyeBlink_R", QPair("au_reye_closed", 1.0f)); - blendshapeMap.insert("EyeOpen_L", QPair("au_upper_lid_raiser", 1.0f)); - blendshapeMap.insert("EyeOpen_R", QPair("au_upper_lid_raiser", 1.0f)); - blendshapeMap.insert("LipLowerOpen", QPair("au_lower_lip_x_push", 1.0f)); - blendshapeMap.insert("LipsStretch_L", QPair("au_lip_stretcher_right", 0.5f)); - blendshapeMap.insert("BrowsU_L", QPair("au_right_outer_brow_raiser", 1.0f)); - blendshapeMap.insert("BrowsU_C", QPair("au_right_inner_brow_raiser", 1.0f)); - blendshapeMap.insert("BrowsD_L", QPair("au_right_brow_lowerer", 1.0f)); - - QMultiHash > actionUnitNameMap; - for (int i = 0;; i++) { - QByteArray blendshape = FACESHIFT_BLENDSHAPES[i]; - if (blendshape.isEmpty()) { - break; - } - if (blendshape == "EyeBlink_L") { - leftEyeBlinkIndex = i; - - } else if (blendshape == "EyeBlink_R") { - rightEyeBlinkIndex = i; - } - for (QMultiHash >::const_iterator it = blendshapeMap.constFind(blendshape); - it != blendshapeMap.constEnd() && it.key() == blendshape; it++) { - actionUnitNameMap.insert(it.value().first, QPair(i, it.value().second)); - } - } - - return actionUnitNameMap; -} - -static const QMultiHash >& getActionUnitNameMap() { - static QMultiHash > actionUnitNameMap = createActionUnitNameMap(); - return actionUnitNameMap; -} -#endif - - -#ifdef HAVE_VISAGE -const float TRANSLATION_SCALE = 20.0f; -void Visage::init() { - connect(DependencyManager::get().data(), SIGNAL(connectionStateChanged()), SLOT(updateEnabled())); - updateEnabled(); -} - -void Visage::update(float deltaTime) { - if (!isActive()) { - return; - } - FaceTracker::update(deltaTime); - - _headRotation = glm::quat(glm::vec3(-_data->faceRotation[0], -_data->faceRotation[1], _data->faceRotation[2])); - _headTranslation = (glm::vec3(_data->faceTranslation[0], _data->faceTranslation[1], _data->faceTranslation[2]) - - _headOrigin) * TRANSLATION_SCALE; - _estimatedEyePitch = glm::degrees(-_data->gazeDirection[1]); - _estimatedEyeYaw = glm::degrees(-_data->gazeDirection[0]); - - if (_actionUnitIndexMap.isEmpty()) { - int maxIndex = -1; - for (int i = 0; i < _data->actionUnitCount; i++) { - QByteArray name = _data->actionUnitsNames[i]; - for (QMultiHash >::const_iterator it = getActionUnitNameMap().constFind(name); - it != getActionUnitNameMap().constEnd() && it.key() == name; it++) { - _actionUnitIndexMap.insert(i, it.value()); - maxIndex = qMax(maxIndex, it.value().first); - } - } - _blendshapeCoefficients.resize(maxIndex + 1); - } - - qFill(_blendshapeCoefficients.begin(), _blendshapeCoefficients.end(), 0.0f); - for (int i = 0; i < _data->actionUnitCount; i++) { - if (!_data->actionUnitsUsed[i]) { - continue; - } - for (QMultiHash >::const_iterator it = _actionUnitIndexMap.constFind(i); - it != _actionUnitIndexMap.constEnd() && it.key() == i; it++) { - _blendshapeCoefficients[it.value().first] += _data->actionUnits[i] * it.value().second; - } - } - _blendshapeCoefficients[leftEyeBlinkIndex] = 1.0f - _data->eyeClosure[1]; - _blendshapeCoefficients[rightEyeBlinkIndex] = 1.0f - _data->eyeClosure[0]; -} - -void Visage::reset() { - _headOrigin += _headTranslation / TRANSLATION_SCALE; -} -#endif - -void Visage::updateEnabled() { - setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) && - !(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && - DependencyManager::get()->isConnectedOrConnecting()) && - !Menu::getInstance()->isOptionChecked(MenuOption::DDEFaceRegression)); -} - -void Visage::setEnabled(bool enabled) { -#ifdef HAVE_VISAGE - if (_enabled == enabled) { - return; - } - if ((_enabled = enabled)) { - _tracker->trackFromCam(); - } else { - _tracker->stop(); - } -#endif -} diff --git a/interface/src/devices/Visage.h b/interface/src/devices/Visage.h deleted file mode 100644 index 3ff1ea8c27..0000000000 --- a/interface/src/devices/Visage.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// Visage.h -// interface/src/devices -// -// Created by Andrzej Kapolka on 2/11/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_Visage_h -#define hifi_Visage_h - -#include -#include -#include - -#include - -#include "FaceTracker.h" - -namespace VisageSDK { - class VisageTracker2; - struct FaceData; -} - -/// Handles input from the Visage webcam feature tracking software. -class Visage : public FaceTracker, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: -#ifdef HAVE_VISAGE - virtual void init(); - virtual void update(float deltaTime); - virtual void reset(); - - virtual bool isActive() const { return _tracker->getTrackingData(_data) == TRACK_STAT_OK; } - virtual bool isTracking() const { return isActive(); } -#endif - -public slots: - void updateEnabled(); - -private: - Visage(); - virtual ~Visage(); - -#ifdef HAVE_VISAGE - VisageSDK::VisageTracker2* _tracker; - VisageSDK::FaceData* _data; - QMultiHash > _actionUnitIndexMap; -#endif - - void setEnabled(bool enabled); - - bool _enabled; - - glm::vec3 _headOrigin; -}; - -#endif // hifi_Visage_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index b4486ceb2b..7112944375 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -42,7 +42,7 @@ static BOOL CALLBACK enumWindowsCallback(HWND hWnd, LPARAM lParam) { int main(int argc, const char* argv[]) { #ifdef Q_OS_WIN // Run only one instance of Interface at a time. - HANDLE mutex = CreateMutex(NULL, FALSE, "High Fidelity Interface"); + HANDLE mutex = CreateMutex(NULL, FALSE, "High Fidelity Interface - " + qgetenv("USERNAME")); DWORD result = GetLastError(); if (result == ERROR_ALREADY_EXISTS || result == ERROR_ACCESS_DENIED) { // Interface is already running. @@ -112,6 +112,7 @@ int main(int argc, const char* argv[]) { exitCode = app.exec(); } + OculusManager::deinit(); #ifdef Q_OS_WIN ReleaseMutex(mutex); #endif diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 1c9fced619..be36fe1989 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "Application.h" #include "ui/DataWebPage.h" @@ -72,6 +73,11 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid _windowWidget = dialogWidget; } + auto style = QStyleFactory::create("fusion"); + if (style) { + _webView->setStyle(style); + } + _webView->setPage(new DataWebPage()); _webView->setUrl(url); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 6ed3e48274..bd2903863d 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -45,13 +45,19 @@ QScriptValue WindowScriptingInterface::hasFocus() { } void WindowScriptingInterface::setFocus() { - auto window = Application::getInstance()->getWindow(); - window->activateWindow(); - window->setFocus(); + // It's forbidden to call focus() from another thread. + Application::getInstance()->postLambdaEvent([] { + auto window = Application::getInstance()->getWindow(); + window->activateWindow(); + window->setFocus(); + }); } void WindowScriptingInterface::raiseMainWindow() { - Application::getInstance()->getWindow()->raise(); + // It's forbidden to call raise() from another thread. + Application::getInstance()->postLambdaEvent([] { + Application::getInstance()->getWindow()->raise(); + }); } void WindowScriptingInterface::setCursorVisible(bool visible) { diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index b414f95240..837702d253 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -1,148 +1,47 @@ // // AddressBarDialog.cpp -// interface/src/ui // -// Created by Stojce Slavkovski on 9/22/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AddressBarDialog.h" + #include -#include - -#include "AddressBarDialog.h" +#include "DependencyManager.h" #include "AddressManager.h" -#include "Application.h" -#include "MainWindow.h" -const QString ADDRESSBAR_GO_BUTTON_ICON = "images/address-bar-submit.svg"; -const QString ADDRESSBAR_GO_BUTTON_ACTIVE_ICON = "images/address-bar-submit-active.svg"; +HIFI_QML_DEF(AddressBarDialog) -AddressBarDialog::AddressBarDialog(QWidget* parent) : - FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP) -{ - setAttribute(Qt::WA_DeleteOnClose, false); - setupUI(); - +AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(parent) { auto addressManager = DependencyManager::get(); - connect(addressManager.data(), &AddressManager::lookupResultIsOffline, this, &AddressBarDialog::displayAddressOfflineMessage); connect(addressManager.data(), &AddressManager::lookupResultIsNotFound, this, &AddressBarDialog::displayAddressNotFoundMessage); + connect(addressManager.data(), &AddressManager::lookupResultsFinished, this, &AddressBarDialog::hide); } -void AddressBarDialog::setupUI() { - - const QString DIALOG_STYLESHEET = "font-family: Helvetica, Arial, sans-serif;"; - const QString ADDRESSBAR_PLACEHOLDER = "Go to: domain, location, @user, /x,y,z"; - const QString ADDRESSBAR_STYLESHEET = "padding: 5px 10px; font-size: 20px;"; - - const int ADDRESSBAR_MIN_WIDTH = 200; - const int ADDRESSBAR_MAX_WIDTH = 615; - const int ADDRESSBAR_HEIGHT = 42; - const int ADDRESSBAR_STRETCH = 60; - - const int BUTTON_SPACER_SIZE = 5; - const int DEFAULT_SPACER_SIZE = 20; - const int ADDRESS_LAYOUT_RIGHT_MARGIN = 10; - - const int GO_BUTTON_SIZE = 42; - const int CLOSE_BUTTON_SIZE = 16; - const QString CLOSE_BUTTON_ICON = "styles/close.svg"; - - const int DIALOG_HEIGHT = 62; - const int DIALOG_INITIAL_WIDTH = 560; - - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setSizePolicy(sizePolicy); - setMinimumSize(QSize(DIALOG_INITIAL_WIDTH, DIALOG_HEIGHT)); - setMaximumHeight(DIALOG_HEIGHT); - setStyleSheet(DIALOG_STYLESHEET); - - _verticalLayout = new QVBoxLayout(this); - _verticalLayout->setContentsMargins(0, 0, 0, 0); - - _addressLayout = new QHBoxLayout(); - _addressLayout->setContentsMargins(0, 0, ADDRESS_LAYOUT_RIGHT_MARGIN, 0); - - _leftSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE, - DEFAULT_SPACER_SIZE, - QSizePolicy::MinimumExpanding, - QSizePolicy::Minimum); - - _addressLayout->addItem(_leftSpacer); - - _addressLineEdit = new QLineEdit(this); - _addressLineEdit->setAttribute(Qt::WA_MacShowFocusRect, 0); - _addressLineEdit->setPlaceholderText(ADDRESSBAR_PLACEHOLDER); - QSizePolicy sizePolicyLineEdit(QSizePolicy::Preferred, QSizePolicy::Fixed); - sizePolicyLineEdit.setHorizontalStretch(ADDRESSBAR_STRETCH); - _addressLineEdit->setSizePolicy(sizePolicyLineEdit); - _addressLineEdit->setMinimumSize(QSize(ADDRESSBAR_MIN_WIDTH, ADDRESSBAR_HEIGHT)); - _addressLineEdit->setMaximumSize(QSize(ADDRESSBAR_MAX_WIDTH, ADDRESSBAR_HEIGHT)); - _addressLineEdit->setStyleSheet(ADDRESSBAR_STYLESHEET); - _addressLayout->addWidget(_addressLineEdit); - - _buttonSpacer = new QSpacerItem(BUTTON_SPACER_SIZE, BUTTON_SPACER_SIZE, QSizePolicy::Fixed, QSizePolicy::Minimum); - _addressLayout->addItem(_buttonSpacer); - - _goButton = new QPushButton(this); - _goButton->setSizePolicy(sizePolicy); - _goButton->setMinimumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE)); - _goButton->setMaximumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE)); - _goButton->setIcon(QIcon(PathUtils::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON)); - _goButton->setIconSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE)); - _goButton->setDefault(true); - _goButton->setFlat(true); - _addressLayout->addWidget(_goButton); - - _rightSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE, - DEFAULT_SPACER_SIZE, - QSizePolicy::MinimumExpanding, - QSizePolicy::Minimum); - - _addressLayout->addItem(_rightSpacer); - - _closeButton = new QPushButton(this); - _closeButton->setSizePolicy(sizePolicy); - _closeButton->setMinimumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); - _closeButton->setMaximumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); - QIcon icon(PathUtils::resourcesPath() + CLOSE_BUTTON_ICON); - _closeButton->setIcon(icon); - _closeButton->setIconSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); - _closeButton->setFlat(true); - _addressLayout->addWidget(_closeButton, 0, Qt::AlignRight); - - _verticalLayout->addLayout(_addressLayout); - - connect(_goButton, &QPushButton::clicked, this, &AddressBarDialog::accept); - connect(_closeButton, &QPushButton::clicked, this, &QDialog::close); +void AddressBarDialog::hide() { + ((QQuickItem*)parent())->setEnabled(false); } -void AddressBarDialog::showEvent(QShowEvent* event) { - _goButton->setIcon(QIcon(PathUtils::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON)); - _addressLineEdit->setText(QString()); - _addressLineEdit->setFocus(); - FramelessDialog::showEvent(event); -} - -void AddressBarDialog::accept() { - if (!_addressLineEdit->text().isEmpty()) { - _goButton->setIcon(QIcon(PathUtils::resourcesPath() + ADDRESSBAR_GO_BUTTON_ACTIVE_ICON)); - auto addressManager = DependencyManager::get(); - connect(addressManager.data(), &AddressManager::lookupResultsFinished, this, &QDialog::hide); - addressManager->handleLookupString(_addressLineEdit->text()); +void AddressBarDialog::loadAddress(const QString& address) { + qDebug() << "Called LoadAddress with address " << address; + if (!address.isEmpty()) { + DependencyManager::get()->handleLookupString(address); } } void AddressBarDialog::displayAddressOfflineMessage() { - QMessageBox::information(Application::getInstance()->getWindow(), "Address offline", - "That user or place is currently offline."); + OffscreenUi::information("Address offline", + "That user or place is currently offline."); } void AddressBarDialog::displayAddressNotFoundMessage() { - QMessageBox::information(Application::getInstance()->getWindow(), "Address not found", - "There is no address information for that user or place."); -} \ No newline at end of file + OffscreenUi::information("Address not found", + "There is no address information for that user or place."); +} + diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index dda807d6e2..395cf3c725 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -1,47 +1,33 @@ // // AddressBarDialog.h -// interface/src/ui // -// Created by Stojce Slavkovski on 9/22/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// 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 // +#pragma once #ifndef hifi_AddressBarDialog_h #define hifi_AddressBarDialog_h -#include "FramelessDialog.h" +#include -#include -#include -#include -#include - -class AddressBarDialog : public FramelessDialog { +class AddressBarDialog : public OffscreenQmlDialog +{ Q_OBJECT + HIFI_QML_DECL public: - AddressBarDialog(QWidget* parent); - -private: - void setupUI(); - void showEvent(QShowEvent* event); - - QVBoxLayout *_verticalLayout; - QHBoxLayout *_addressLayout; - QSpacerItem *_leftSpacer; - QSpacerItem *_rightSpacer; - QSpacerItem *_buttonSpacer; - QPushButton *_goButton; - QPushButton *_closeButton; - QLineEdit *_addressLineEdit; - -private slots: - void accept(); + AddressBarDialog(QQuickItem* parent = nullptr); + +protected: void displayAddressOfflineMessage(); void displayAddressNotFoundMessage(); + void hide(); + + Q_INVOKABLE void loadAddress(const QString& address); }; -#endif // hifi_AddressBarDialog_h +#endif diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index f08df229cc..8704a61261 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "AudioClient.h" #include "audio/AudioIOStatsRenderer.h" @@ -161,13 +162,27 @@ ApplicationOverlay::ApplicationOverlay() : _domainStatusBorder = geometryCache->allocateID(); _magnifierBorder = geometryCache->allocateID(); + // Once we move UI rendering and screen rendering to different + // threads, we need to use a sync object to deteremine when + // the current UI texture is no longer being read from, and only + // then release it back to the UI for re-use + auto offscreenUi = DependencyManager::get(); + connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->lockTexture(textureId); + assert(!glGetError()); + std::swap(_newUiTexture, textureId); + if (textureId) { + offscreenUi->releaseTexture(textureId); + } + }); } ApplicationOverlay::~ApplicationOverlay() { } // Renders the overlays either to a texture or to the screen -void ApplicationOverlay::renderOverlay(bool renderToTexture) { +void ApplicationOverlay::renderOverlay() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); Overlays& overlays = qApp->getOverlays(); auto glCanvas = Application::getInstance()->getGLWidget(); @@ -183,11 +198,9 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - if (renderToTexture) { - _overlays.buildFramebufferObject(); - _overlays.bind(); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } + _overlays.buildFramebufferObject(); + _overlays.bind(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); { const float NEAR_CLIP = -10000; @@ -218,9 +231,25 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glEnable(GL_LIGHTING); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - if (renderToTexture) { - _overlays.release(); + _overlays.release(); +} + +// A quick and dirty solution for compositing the old overlay +// texture with the new one +template +void with_each_texture(GLuint firstPassTexture, GLuint secondPassTexture, F f) { + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + if (firstPassTexture) { + glBindTexture(GL_TEXTURE_2D, firstPassTexture); + f(); } + if (secondPassTexture) { + glBindTexture(GL_TEXTURE_2D, secondPassTexture); + f(); + } + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); } // Draws the FBO texture for the screen @@ -228,31 +257,24 @@ void ApplicationOverlay::displayOverlayTexture() { if (_alpha == 0.0f) { return; } - auto glCanvas = Application::getInstance()->getGLWidget(); - - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - _overlays.bindTexture(); - glMatrixMode(GL_PROJECTION); glPushMatrix(); { glLoadIdentity(); - glOrtho(0, glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight(), 0, -1.0, 1.0); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - - glm::vec2 topLeft(0.0f, 0.0f); - glm::vec2 bottomRight(glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight()); - glm::vec2 texCoordTopLeft(0.0f, 1.0f); - glm::vec2 texCoordBottomRight(1.0f, 0.0f); + if (_alpha < 1.0) { + glEnable(GL_BLEND); + } - DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, - glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); - + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + static const glm::vec2 topLeft(-1, 1); + static const glm::vec2 bottomRight(1, -1); + static const glm::vec2 texCoordTopLeft(0.0f, 1.0f); + static const glm::vec2 texCoordBottomRight(1.0f, 0.0f); + DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, + glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); + }); } glPopMatrix(); - - glDisable(GL_TEXTURE_2D); } // Draws the FBO texture for Oculus rift. @@ -260,10 +282,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { if (_alpha == 0.0f) { return; } - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - _overlays.bindTexture(); - + glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glEnable(GL_DEPTH_TEST); @@ -271,8 +290,8 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glDisable(GL_LIGHTING); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.01f); - - + + //Update and draw the magnifiers MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const glm::quat& orientation = myAvatar->getOrientation(); @@ -303,8 +322,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { //Render magnifier, but dont show border for mouse magnifier glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), _reticlePosition[MOUSE].y())); - - renderMagnifier(projection, _magSizeMult[i], i != MOUSE); + with_each_texture(_overlays.getTexture(), 0, [&] { + renderMagnifier(projection, _magSizeMult[i], i != MOUSE); + }); } } @@ -319,12 +339,15 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { _overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80); } - _overlays.render(); + + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + _overlays.render(); + }); + if (!Application::getInstance()->isMouseHidden()) { renderPointersOculus(myAvatar->getDefaultEyePosition()); } glDepthMask(GL_TRUE); - _overlays.releaseTexture(); glDisable(GL_TEXTURE_2D); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); @@ -341,14 +364,10 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const glm::vec3& viewMatrixTranslation = qApp->getViewMatrixTranslation(); - glActiveTexture(GL_TEXTURE0); - glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - _overlays.bindTexture(); glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - glEnable(GL_TEXTURE_2D); glMatrixMode(GL_MODELVIEW); @@ -382,13 +401,15 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as GLfloat y = -halfQuadHeight; glDisable(GL_DEPTH_TEST); - DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), glm::vec3(x + quadWidth, y + quadHeight, -distance), glm::vec3(x + quadWidth, y, -distance), glm::vec3(x, y, -distance), glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), overlayColor); + }); auto glCanvas = Application::getInstance()->getGLWidget(); if (_crosshairTexture == 0) { @@ -993,14 +1014,6 @@ void ApplicationOverlay::TexturedHemisphere::release() { _framebufferObject->release(); } -void ApplicationOverlay::TexturedHemisphere::bindTexture() { - glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); -} - -void ApplicationOverlay::TexturedHemisphere::releaseTexture() { - glBindTexture(GL_TEXTURE_2D, 0); -} - void ApplicationOverlay::TexturedHemisphere::buildVBO(const float fov, const float aspectRatio, const int slices, @@ -1099,14 +1112,14 @@ void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { } _framebufferObject = new QOpenGLFramebufferObject(size, QOpenGLFramebufferObject::Depth); - bindTexture(); + glBindTexture(GL_TEXTURE_2D, getTexture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); GLfloat borderColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - releaseTexture(); + glBindTexture(GL_TEXTURE_2D, 0); } //Renders a hemisphere with texture coordinates. @@ -1137,6 +1150,10 @@ void ApplicationOverlay::TexturedHemisphere::render() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } +GLuint ApplicationOverlay::TexturedHemisphere::getTexture() { + return _framebufferObject->texture(); +} + glm::vec2 ApplicationOverlay::directionToSpherical(glm::vec3 direction) const { glm::vec2 result; // Compute yaw diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index cc424d0c8f..e6c7526c5d 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -23,12 +23,13 @@ const float MAGNIFY_MULT = 2.0f; const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f; // Handles the drawing of the overlays to the screen -class ApplicationOverlay { +class ApplicationOverlay : public QObject { + Q_OBJECT public: ApplicationOverlay(); ~ApplicationOverlay(); - void renderOverlay(bool renderToTexture = false); + void renderOverlay(); void displayOverlayTexture(); void displayOverlayTextureOculus(Camera& whichCamera); void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov); @@ -75,8 +76,7 @@ private: void bind(); void release(); - void bindTexture(); - void releaseTexture(); + GLuint getTexture(); void buildFramebufferObject(); void buildVBO(const float fov, const float aspectRatio, const int slices, const int stacks); @@ -122,6 +122,9 @@ private: float _trailingAudioLoudness; GLuint _crosshairTexture; + // TODO, move divide up the rendering, displaying and input handling + // facilities of this class + GLuint _newUiTexture{ 0 }; int _reticleQuad; int _magnifierQuad; diff --git a/interface/src/ui/AvatarAppearanceDialog.cpp b/interface/src/ui/AvatarAppearanceDialog.cpp new file mode 100644 index 0000000000..ceaaf140c4 --- /dev/null +++ b/interface/src/ui/AvatarAppearanceDialog.cpp @@ -0,0 +1,210 @@ +// +// AvatarAppearanceDialog.cpp +// interface/src/ui +// +// Created by Stojce Slavkovski on 2/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include +#include +#include +#include +#include + +#include "Application.h" +#include "MainWindow.h" +#include "LODManager.h" +#include "Menu.h" +#include "AvatarAppearanceDialog.h" +#include "Snapshot.h" +#include "UserActivityLogger.h" +#include "UIUtil.h" +#include "ui/DialogsManager.h" +#include "ui/PreferencesDialog.h" + +AvatarAppearanceDialog::AvatarAppearanceDialog(QWidget* parent) : + QDialog(parent) { + + setAttribute(Qt::WA_DeleteOnClose); + + ui.setupUi(this); + + loadAvatarAppearance(); + + connect(ui.defaultButton, &QPushButton::clicked, this, &AvatarAppearanceDialog::accept); + + connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &AvatarAppearanceDialog::openHeadModelBrowser); + connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &AvatarAppearanceDialog::openBodyModelBrowser); + connect(ui.buttonBrowseFullAvatar, &QPushButton::clicked, this, &AvatarAppearanceDialog::openFullAvatarModelBrowser); + + connect(ui.useSeparateBodyAndHead, &QRadioButton::clicked, this, &AvatarAppearanceDialog::useSeparateBodyAndHead); + connect(ui.useFullAvatar, &QRadioButton::clicked, this, &AvatarAppearanceDialog::useFullAvatar); + + connect(Application::getInstance(), &Application::headURLChanged, this, &AvatarAppearanceDialog::headURLChanged); + connect(Application::getInstance(), &Application::bodyURLChanged, this, &AvatarAppearanceDialog::bodyURLChanged); + connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &AvatarAppearanceDialog::fullAvatarURLChanged); + + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + ui.bodyNameLabel->setText("Body - " + myAvatar->getBodyModelName()); + ui.headNameLabel->setText("Head - " + myAvatar->getHeadModelName()); + ui.fullAvatarNameLabel->setText("Full Avatar - " + myAvatar->getFullAvartarModelName()); + + UIUtil::scaleWidgetFontSizes(this); +} + +void AvatarAppearanceDialog::useSeparateBodyAndHead(bool checked) { + QUrl headURL(ui.faceURLEdit->text()); + QUrl bodyURL(ui.skeletonURLEdit->text()); + DependencyManager::get()->getMyAvatar()->useHeadAndBodyURLs(headURL, bodyURL); + setUseFullAvatar(!checked); +} + +void AvatarAppearanceDialog::useFullAvatar(bool checked) { + QUrl fullAvatarURL(ui.fullAvatarURLEdit->text()); + DependencyManager::get()->getMyAvatar()->useFullAvatarURL(fullAvatarURL); + setUseFullAvatar(checked); +} + +void AvatarAppearanceDialog::setUseFullAvatar(bool useFullAvatar) { + _useFullAvatar = useFullAvatar; + ui.faceURLEdit->setEnabled(!_useFullAvatar); + ui.skeletonURLEdit->setEnabled(!_useFullAvatar); + ui.fullAvatarURLEdit->setEnabled(_useFullAvatar); + + ui.useFullAvatar->setChecked(_useFullAvatar); + ui.useSeparateBodyAndHead->setChecked(!_useFullAvatar); + + DependencyManager::get()->getPreferencesDialog()->avatarDescriptionChanged(); +} + +void AvatarAppearanceDialog::headURLChanged(const QString& newValue, const QString& modelName) { + ui.faceURLEdit->setText(newValue); + setUseFullAvatar(false); + ui.headNameLabel->setText("Head - " + modelName); +} + +void AvatarAppearanceDialog::bodyURLChanged(const QString& newValue, const QString& modelName) { + ui.skeletonURLEdit->setText(newValue); + setUseFullAvatar(false); + ui.bodyNameLabel->setText("Body - " + modelName); +} + +void AvatarAppearanceDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { + ui.fullAvatarURLEdit->setText(newValue); + setUseFullAvatar(true); + ui.fullAvatarNameLabel->setText("Full Avatar - " + modelName); +} + +void AvatarAppearanceDialog::accept() { + saveAvatarAppearance(); + + DependencyManager::get()->getPreferencesDialog()->avatarDescriptionChanged(); + + close(); + delete _marketplaceWindow; + _marketplaceWindow = NULL; +} + +void AvatarAppearanceDialog::setHeadUrl(QString modelUrl) { + ui.faceURLEdit->setText(modelUrl); +} + +void AvatarAppearanceDialog::setSkeletonUrl(QString modelUrl) { + ui.skeletonURLEdit->setText(modelUrl); +} + +void AvatarAppearanceDialog::openFullAvatarModelBrowser() { + auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; + auto WIDTH = 900; + auto HEIGHT = 700; + if (!_marketplaceWindow) { + _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); + } + _marketplaceWindow->setVisible(true); +} + +void AvatarAppearanceDialog::openHeadModelBrowser() { + auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; + auto WIDTH = 900; + auto HEIGHT = 700; + if (!_marketplaceWindow) { + _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); + } + _marketplaceWindow->setVisible(true); +} + +void AvatarAppearanceDialog::openBodyModelBrowser() { + auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; + auto WIDTH = 900; + auto HEIGHT = 700; + if (!_marketplaceWindow) { + _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); + } + _marketplaceWindow->setVisible(true); +} + +void AvatarAppearanceDialog::resizeEvent(QResizeEvent *resizeEvent) { + + // keep buttons panel at the bottom + ui.buttonsPanel->setGeometry(0, + size().height() - ui.buttonsPanel->height(), + size().width(), + ui.buttonsPanel->height()); + + // set width and height of srcollarea to match bottom panel and width + ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(), + size().width(), + size().height() - ui.buttonsPanel->height() - ui.scrollArea->geometry().y()); + +} + +void AvatarAppearanceDialog::loadAvatarAppearance() { + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + _useFullAvatar = myAvatar->getUseFullAvatar(); + _fullAvatarURLString = myAvatar->getFullAvatarURLFromPreferences().toString(); + _headURLString = myAvatar->getHeadURLFromPreferences().toString(); + _bodyURLString = myAvatar->getBodyURLFromPreferences().toString(); + + ui.fullAvatarURLEdit->setText(_fullAvatarURLString); + ui.faceURLEdit->setText(_headURLString); + ui.skeletonURLEdit->setText(_bodyURLString); + setUseFullAvatar(_useFullAvatar); +} + +void AvatarAppearanceDialog::saveAvatarAppearance() { + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + QUrl headURL(ui.faceURLEdit->text()); + QString headURLString = headURL.toString(); + + QUrl bodyURL(ui.skeletonURLEdit->text()); + QString bodyURLString = bodyURL.toString(); + + QUrl fullAvatarURL(ui.fullAvatarURLEdit->text()); + QString fullAvatarURLString = fullAvatarURL.toString(); + + bool somethingChanged = + _useFullAvatar != myAvatar->getUseFullAvatar() || + fullAvatarURLString != myAvatar->getFullAvatarURLFromPreferences().toString() || + headURLString != myAvatar->getHeadURLFromPreferences().toString() || + bodyURLString != myAvatar->getBodyURLFromPreferences().toString(); + + if (somethingChanged) { + if (_useFullAvatar) { + myAvatar->useFullAvatarURL(fullAvatarURL); + } else { + myAvatar->useHeadAndBodyURLs(headURL, bodyURL); + } + } +} diff --git a/interface/src/ui/AvatarAppearanceDialog.h b/interface/src/ui/AvatarAppearanceDialog.h new file mode 100644 index 0000000000..be30caeedb --- /dev/null +++ b/interface/src/ui/AvatarAppearanceDialog.h @@ -0,0 +1,64 @@ +// +// AvatarAppearanceDialog.h +// interface/src/ui +// +// Created by Stojce Slavkovski on 2/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AvatarAppearanceDialog_h +#define hifi_AvatarAppearanceDialog_h + +#include "ui_avatarAppearance.h" + +#include +#include + +#include "scripting/WebWindowClass.h" + +class AvatarAppearanceDialog : public QDialog { + Q_OBJECT + +public: + AvatarAppearanceDialog(QWidget* parent = nullptr); + +protected: + void resizeEvent(QResizeEvent* resizeEvent); + +private: + void loadAvatarAppearance(); + void saveAvatarAppearance(); + void openHeadModelBrowser(); + void openBodyModelBrowser(); + void openFullAvatarModelBrowser(); + void setUseFullAvatar(bool useFullAvatar); + + Ui_AvatarAppearanceDialog ui; + + bool _useFullAvatar; + QString _headURLString; + QString _bodyURLString; + QString _fullAvatarURLString; + + + QString _displayNameString; + + WebWindowClass* _marketplaceWindow = NULL; + +private slots: + void accept(); + void setHeadUrl(QString modelUrl); + void setSkeletonUrl(QString modelUrl); + void headURLChanged(const QString& newValue, const QString& modelName); + void bodyURLChanged(const QString& newValue, const QString& modelName); + void fullAvatarURLChanged(const QString& newValue, const QString& modelName); + void useSeparateBodyAndHead(bool checked); + void useFullAvatar(bool checked); + + +}; + +#endif // hifi_AvatarAppearanceDialog_h diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 701ceb0189..56dd69eeeb 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DialogsManager.h" + #include #include @@ -18,6 +20,7 @@ #include "AddressBarDialog.h" #include "AnimationsDialog.h" #include "AttachmentsDialog.h" +#include "AvatarAppearanceDialog.h" #include "BandwidthDialog.h" #include "CachesSizeDialog.h" #include "DiskCacheEditor.h" @@ -28,14 +31,9 @@ #include "PreferencesDialog.h" #include "ScriptEditorWindow.h" -#include "DialogsManager.h" void DialogsManager::toggleAddressBar() { - maybeCreateDialog(_addressBarDialog); - - if (!_addressBarDialog->isVisible()) { - _addressBarDialog->show(); - } + AddressBarDialog::toggle(); } void DialogsManager::toggleDiskCacheEditor() { @@ -44,13 +42,11 @@ void DialogsManager::toggleDiskCacheEditor() { } void DialogsManager::toggleLoginDialog() { - maybeCreateDialog(_loginDialog); - _loginDialog->toggleQAction(); + LoginDialog::toggleAction(); } void DialogsManager::showLoginDialog() { - maybeCreateDialog(_loginDialog); - _loginDialog->showLoginForCurrentDomain(); + LoginDialog::show(); } void DialogsManager::octreeStatsDetails() { @@ -85,6 +81,15 @@ void DialogsManager::editPreferences() { } } +void DialogsManager::changeAvatarAppearance() { + if (!_avatarAppearanceDialog) { + maybeCreateDialog(_avatarAppearanceDialog); + _avatarAppearanceDialog->show(); + } else { + _avatarAppearanceDialog->close(); + } +} + void DialogsManager::editAttachments() { if (!_attachmentsDialog) { maybeCreateDialog(_attachmentsDialog); @@ -170,3 +175,4 @@ void DialogsManager::showIRCLink() { _ircInfoBox->raise(); } + diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 897215cbff..d5d9c33a9a 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -33,6 +33,7 @@ class OctreeStatsDialog; class PreferencesDialog; class ScriptEditorWindow; class QMessageBox; +class AvatarAppearanceDialog; class DialogsManager : public QObject, public Dependency { Q_OBJECT @@ -43,6 +44,7 @@ public: QPointer getHMDToolsDialog() const { return _hmdToolsDialog; } QPointer getLodToolsDialog() const { return _lodToolsDialog; } QPointer getOctreeStatsDialog() const { return _octreeStatsDialog; } + QPointer getPreferencesDialog() const { return _preferencesDialog; } public slots: void toggleAddressBar(); @@ -59,6 +61,7 @@ public slots: void hmdTools(bool showTools); void showScriptEditor(); void showIRCLink(); + void changeAvatarAppearance(); private slots: void toggleToolWindow(); @@ -94,6 +97,7 @@ private: QPointer _octreeStatsDialog; QPointer _preferencesDialog; QPointer _scriptEditor; + QPointer _avatarAppearanceDialog; }; #endif // hifi_DialogsManager_h \ No newline at end of file diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp deleted file mode 100644 index bae6217083..0000000000 --- a/interface/src/ui/FramelessDialog.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// -// FramelessDialog.cpp -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/20/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include - -#include - -#include "Application.h" -#include "FramelessDialog.h" -#include "Menu.h" - -const int RESIZE_HANDLE_WIDTH = 7; - -FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags, Position position) : - QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint), - _allowResize(true), - _isResizing(false), - _resizeInitialWidth(0), - _selfHidden(false), - _position(position), - _hideOnBlur(true) { - - setAttribute(Qt::WA_DeleteOnClose); - - // handle rezize and move events - parentWidget()->installEventFilter(this); - - // handle minimize, restore and focus events - Application::getInstance()->installEventFilter(this); -} - -bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) { - switch (event->type()) { - case QEvent::Move: - if (sender == parentWidget()) { - resizeAndPosition(false); - } - break; - case QEvent::Resize: - if (sender == parentWidget()) { - resizeAndPosition(false); - } - break; - case QEvent::WindowStateChange: - if (_hideOnBlur && parentWidget()->isMinimized()) { - if (isVisible()) { - _selfHidden = true; - setHidden(true); - } - } else if (_selfHidden) { - _selfHidden = false; - setHidden(false); - } - break; - case QEvent::ApplicationDeactivate: - // hide on minimize and focus lost - if (_hideOnBlur && isVisible()) { - _selfHidden = true; - setHidden(true); - } - break; - case QEvent::ApplicationActivate: - if (_selfHidden) { - _selfHidden = false; - setHidden(false); - } - break; - default: - break; - } - - return false; -} - -void FramelessDialog::setStyleSheetFile(const QString& fileName) { - QFile globalStyleSheet(PathUtils::resourcesPath() + "styles/global.qss"); - QFile styleSheet(PathUtils::resourcesPath() + fileName); - if (styleSheet.open(QIODevice::ReadOnly) && globalStyleSheet.open(QIODevice::ReadOnly) ) { - QDir::setCurrent(PathUtils::resourcesPath()); - setStyleSheet(globalStyleSheet.readAll() + styleSheet.readAll()); - } -} - -void FramelessDialog::showEvent(QShowEvent* event) { - resizeAndPosition(); - QDialog::showEvent(event); -} - -void FramelessDialog::resizeAndPosition(bool resizeParent) { - QRect parentGeometry = Application::getInstance()->getDesirableApplicationGeometry(); - QSize parentSize = parentGeometry.size(); - - // keep full app height or width depending on position - if (_position == POSITION_LEFT || _position == POSITION_RIGHT) { - setFixedHeight(parentSize.height()); - } else { - setFixedWidth(parentSize.width()); - } - - // resize parrent if width is smaller than this dialog - if (resizeParent && parentSize.width() < size().width()) { - parentWidget()->resize(size().width(), parentSize.height()); - } - - if (_position == POSITION_LEFT || _position == POSITION_TOP) { - // move to upper left corner - move(parentGeometry.topLeft()); - } else if (_position == POSITION_RIGHT) { - // move to upper right corner - QPoint pos = parentGeometry.topRight(); - pos.setX(pos.x() - size().width()); - move(pos); - } - repaint(); -} - -void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) { - if (_allowResize && mouseEvent->button() == Qt::LeftButton) { - if (_position == POSITION_LEFT || _position == POSITION_RIGHT) { - bool hitLeft = (_position == POSITION_LEFT) && (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH); - bool hitRight = (_position == POSITION_RIGHT) && (mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH); - if (hitLeft || hitRight) { - _isResizing = true; - _resizeInitialWidth = size().width(); - setCursor(Qt::SizeHorCursor); - } - } else { - bool hitTop = (_position == POSITION_TOP) && (abs(mouseEvent->pos().y() - size().height()) < RESIZE_HANDLE_WIDTH); - if (hitTop) { - _isResizing = true; - _resizeInitialWidth = size().height(); - setCursor(Qt::SizeHorCursor); - } - } - } -} - -void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) { - unsetCursor(); - _isResizing = false; -} - -void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) { - if (_isResizing) { - if (_position == POSITION_LEFT) { - resize(mouseEvent->pos().x(), size().height()); - } else if (_position == POSITION_RIGHT) { - setUpdatesEnabled(false); - resize(_resizeInitialWidth - mouseEvent->pos().x(), size().height()); - resizeAndPosition(); - _resizeInitialWidth = size().width(); - setUpdatesEnabled(true); - } else if (_position == POSITION_TOP) { - resize(size().width(), mouseEvent->pos().y()); - } - } -} diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h deleted file mode 100644 index 32451f746d..0000000000 --- a/interface/src/ui/FramelessDialog.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// FramelessDialog.h -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/20/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#ifndef hifi_FramelessDialog_h -#define hifi_FramelessDialog_h - -#include - -class FramelessDialog : public QDialog { - Q_OBJECT - -public: - enum Position { POSITION_LEFT, POSITION_RIGHT, POSITION_TOP }; - - FramelessDialog(QWidget* parent, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT); - void setStyleSheetFile(const QString& fileName); - void setAllowResize(bool allowResize) { _allowResize = allowResize; } - bool getAllowResize() { return _allowResize; } - void setHideOnBlur(bool hideOnBlur) { _hideOnBlur = hideOnBlur; } - bool getHideOnBlur() { return _hideOnBlur; } - void resizeAndPosition(bool resizeParent = true); - -protected: - virtual void mouseMoveEvent(QMouseEvent* mouseEvent); - virtual void mousePressEvent(QMouseEvent* mouseEvent); - virtual void mouseReleaseEvent(QMouseEvent* mouseEvent); - virtual void showEvent(QShowEvent* event); - - bool eventFilter(QObject* sender, QEvent* event); - -private: - bool _allowResize; - bool _isResizing; - int _resizeInitialWidth; - bool _selfHidden; ///< true when the dialog itself because of a window event (deactivation or minimization) - Position _position; - bool _hideOnBlur; - -}; - -#endif // hifi_FramelessDialog_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index b134f7f230..b452f153f0 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -1,120 +1,35 @@ // +// // LoginDialog.cpp -// interface/src/ui // -// Created by Ryan Huffman on 4/23/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - - -#include -#include -#include - -#include -#include - -#include "Application.h" -#include "Menu.h" -#include "AccountManager.h" -#include "ui_loginDialog.h" #include "LoginDialog.h" -#include "UIUtil.h" -const QString CREATE_ACCOUNT_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/signup"; -const QString FORGOT_PASSWORD_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/users/password/new"; +#include "DependencyManager.h" +#include "AccountManager.h" +#include "Menu.h" +#include -LoginDialog::LoginDialog(QWidget* parent) : - FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP), - _ui(new Ui::LoginDialog) { - - _ui->setupUi(this); - reset(); - - setAttribute(Qt::WA_DeleteOnClose, false); +HIFI_QML_DEF(LoginDialog) +LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent), _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString()) { connect(&AccountManager::getInstance(), &AccountManager::loginComplete, - this, &LoginDialog::handleLoginCompleted); + this, &LoginDialog::handleLoginCompleted); connect(&AccountManager::getInstance(), &AccountManager::loginFailed, - this, &LoginDialog::handleLoginFailed); - connect(_ui->loginButton, &QPushButton::clicked, - this, &LoginDialog::handleLoginClicked); - connect(_ui->closeButton, &QPushButton::clicked, - this, &LoginDialog::close); - - UIUtil::scaleWidgetFontSizes(this); - _ui->accountLabel->setText(_ui->accountLabel->text().arg(CREATE_ACCOUNT_URL, FORGOT_PASSWORD_URL)); - - // Initialize toggle connection - toggleQAction(); -}; - -LoginDialog::~LoginDialog() { - delete _ui; -}; - -void LoginDialog::reset() { - _ui->errorLabel->hide(); - _ui->emailLineEdit->setFocus(); - _ui->logoLabel->setPixmap(QPixmap(PathUtils::resourcesPath() + "images/hifi-logo.svg")); - _ui->loginButton->setIcon(QIcon(PathUtils::resourcesPath() + "images/login.svg")); - _ui->closeButton->setIcon(QIcon(PathUtils::resourcesPath() + "images/close.svg")); - _ui->infoLabel->setVisible(false); - _ui->errorLabel->setVisible(false); - - _ui->emailLineEdit->setText(""); - _ui->passwordLineEdit->setText(""); - _ui->loginArea->setDisabled(false); + this, &LoginDialog::handleLoginFailed); } -void LoginDialog::handleLoginCompleted(const QUrl& authURL) { - reset(); - close(); -}; - -void LoginDialog::handleLoginFailed() { - _ui->infoLabel->setVisible(false); - _ui->errorLabel->setVisible(true); - - _ui->errorLabel->show(); - _ui->loginArea->setDisabled(false); - - // Move focus to password and select the entire line - _ui->passwordLineEdit->setFocus(); - _ui->passwordLineEdit->setSelection(0, _ui->emailLineEdit->maxLength()); -}; - -void LoginDialog::handleLoginClicked() { - // If the email or password inputs are empty, move focus to them, otherwise attempt to login. - if (_ui->emailLineEdit->text().isEmpty()) { - _ui->emailLineEdit->setFocus(); - } else if (_ui->passwordLineEdit->text().isEmpty()) { - _ui->passwordLineEdit->setFocus(); - } else { - _ui->infoLabel->setVisible(true); - _ui->errorLabel->setVisible(false); - - _ui->loginArea->setDisabled(true); - AccountManager::getInstance().requestAccessToken(_ui->emailLineEdit->text(), _ui->passwordLineEdit->text()); - } -}; - -void LoginDialog::moveEvent(QMoveEvent* event) { - // Modal dialogs seemed to get repositioned automatically. Combat this by moving the window if needed. - resizeAndPosition(); -}; - - -void LoginDialog::toggleQAction() { +void LoginDialog::toggleAction() { AccountManager& accountManager = AccountManager::getInstance(); QAction* loginAction = Menu::getInstance()->getActionForOption(MenuOption::Login); Q_CHECK_PTR(loginAction); - disconnect(loginAction, 0, 0, 0); - + if (accountManager.isLoggedIn()) { // change the menu item to logout loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername()); @@ -122,11 +37,41 @@ void LoginDialog::toggleQAction() { } else { // change the menu item to login loginAction->setText("Login"); - connect(loginAction, &QAction::triggered, this, &LoginDialog::showLoginForCurrentDomain); + connect(loginAction, &QAction::triggered, [] { + LoginDialog::show(); + }); } } -void LoginDialog::showLoginForCurrentDomain() { - show(); - resizeAndPosition(false); +void LoginDialog::handleLoginCompleted(const QUrl&) { + hide(); +} + +void LoginDialog::handleLoginFailed() { + setStatusText("Invalid username or password.< / font>"); +} + +void LoginDialog::setStatusText(const QString& statusText) { + if (statusText != _statusText) { + _statusText = statusText; + emit statusTextChanged(); + } +} + +QString LoginDialog::statusText() const { + return _statusText; +} + +QString LoginDialog::rootUrl() const { + return _rootUrl; +} + +void LoginDialog::login(const QString& username, const QString& password) { + qDebug() << "Attempting to login " << username; + setStatusText("Authenticating..."); + AccountManager::getInstance().requestAccessToken(username, password); +} + +void LoginDialog::openUrl(const QString& url) { + qDebug() << url; } diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index d5384c4625..e9ae0a1c16 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -1,46 +1,49 @@ // // LoginDialog.h -// interface/src/ui // -// Created by Ryan Huffman on 4/23/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2015/04/14 +// 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 // +#pragma once #ifndef hifi_LoginDialog_h #define hifi_LoginDialog_h -#include -#include "FramelessDialog.h" +#include -namespace Ui { - class LoginDialog; -} - -class LoginDialog : public FramelessDialog { +class LoginDialog : public OffscreenQmlDialog +{ Q_OBJECT + HIFI_QML_DECL + + Q_PROPERTY(QString statusText READ statusText WRITE setStatusText NOTIFY statusTextChanged) + Q_PROPERTY(QString rootUrl READ rootUrl) public: - LoginDialog(QWidget* parent); - ~LoginDialog(); + static void toggleAction(); -public slots: - void toggleQAction(); - void showLoginForCurrentDomain(); - -protected slots: - void reset(); - void handleLoginClicked(); + LoginDialog(QQuickItem* parent = nullptr); + + void setStatusText(const QString& statusText); + QString statusText() const; + + QString rootUrl() const; + +signals: + void statusTextChanged(); + +protected: void handleLoginCompleted(const QUrl& authURL); void handleLoginFailed(); -protected: - void moveEvent(QMoveEvent* event); - + Q_INVOKABLE void login(const QString& username, const QString& password); + Q_INVOKABLE void openUrl(const QString& url); private: - Ui::LoginDialog* _ui = nullptr; + QString _statusText; + const QString _rootUrl; }; #endif // hifi_LoginDialog_h diff --git a/interface/src/ui/MarketplaceDialog.cpp b/interface/src/ui/MarketplaceDialog.cpp new file mode 100644 index 0000000000..b9c640054c --- /dev/null +++ b/interface/src/ui/MarketplaceDialog.cpp @@ -0,0 +1,29 @@ +// +// MarketplaceDialog.cpp +// +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Application.h" +#include "MarketplaceDialog.h" +#include "DependencyManager.h" + +HIFI_QML_DEF(MarketplaceDialog) + + +MarketplaceDialog::MarketplaceDialog(QQuickItem* parent) : OffscreenQmlDialog(parent) { +} + +bool MarketplaceDialog::navigationRequested(const QString& url) { + qDebug() << url; + if (Application::getInstance()->canAcceptURL(url)) { + if (Application::getInstance()->acceptURL(url)) { + return false; // we handled it, so QWebPage doesn't need to handle it + } + } + return true; +} diff --git a/interface/src/ui/MarketplaceDialog.h b/interface/src/ui/MarketplaceDialog.h new file mode 100644 index 0000000000..2440c3e07c --- /dev/null +++ b/interface/src/ui/MarketplaceDialog.h @@ -0,0 +1,29 @@ +// +// MarketplaceDialog.h +// +// Created by Bradley Austin Davis on 2015/04/14 +// 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 +// + +#pragma once +#ifndef hifi_MarketplaceDialog_h +#define hifi_MarketplaceDialog_h + +#include + +class MarketplaceDialog : public OffscreenQmlDialog +{ + Q_OBJECT + HIFI_QML_DECL + +public: + MarketplaceDialog(QQuickItem* parent = nullptr); + + Q_INVOKABLE bool navigationRequested(const QString& url); + +}; + +#endif diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index bd32fc7c34..98f7382097 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -19,6 +19,7 @@ #include #include "Application.h" +#include "DialogsManager.h" #include "MainWindow.h" #include "LODManager.h" #include "Menu.h" @@ -41,30 +42,40 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : ui.outputBufferSizeSpinner->setMinimum(MIN_AUDIO_OUTPUT_BUFFER_SIZE_FRAMES); ui.outputBufferSizeSpinner->setMaximum(MAX_AUDIO_OUTPUT_BUFFER_SIZE_FRAMES); - connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser); - connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser); connect(ui.buttonBrowseLocation, &QPushButton::clicked, this, &PreferencesDialog::openSnapshotLocationBrowser); connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser); - connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, - Application::getInstance(), &Application::loadDefaultScripts); + connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, Application::getInstance(), &Application::loadDefaultScripts); + DialogsManager* dialogsManager = DependencyManager::get().data(); + connect(ui.buttonChangeApperance, &QPushButton::clicked, dialogsManager, &DialogsManager::changeAvatarAppearance); - connect(Application::getInstance(), &Application::faceURLChanged, this, &PreferencesDialog::faceURLChanged); - connect(Application::getInstance(), &Application::skeletonURLChanged, this, &PreferencesDialog::skeletonURLChanged); + connect(Application::getInstance(), &Application::headURLChanged, this, &PreferencesDialog::headURLChanged); + connect(Application::getInstance(), &Application::bodyURLChanged, this, &PreferencesDialog::bodyURLChanged); + connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); // move dialog to left side move(parentWidget()->geometry().topLeft()); setFixedHeight(parentWidget()->size().height() - PREFERENCES_HEIGHT_PADDING); + ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); + UIUtil::scaleWidgetFontSizes(this); } -void PreferencesDialog::faceURLChanged(const QString& newValue) { - ui.faceURLEdit->setText(newValue); +void PreferencesDialog::avatarDescriptionChanged() { + ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); } -void PreferencesDialog::skeletonURLChanged(const QString& newValue) { - ui.skeletonURLEdit->setText(newValue); +void PreferencesDialog::headURLChanged(const QString& newValue, const QString& modelName) { + ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); +} + +void PreferencesDialog::bodyURLChanged(const QString& newValue, const QString& modelName) { + ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); +} + +void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { + ui.apperanceDescription->setText(DependencyManager::get()->getMyAvatar()->getModelDescription()); } void PreferencesDialog::accept() { @@ -74,34 +85,6 @@ void PreferencesDialog::accept() { _marketplaceWindow = NULL; } -void PreferencesDialog::setHeadUrl(QString modelUrl) { - ui.faceURLEdit->setText(modelUrl); -} - -void PreferencesDialog::setSkeletonUrl(QString modelUrl) { - ui.skeletonURLEdit->setText(modelUrl); -} - -void PreferencesDialog::openHeadModelBrowser() { - auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; - auto WIDTH = 900; - auto HEIGHT = 700; - if (!_marketplaceWindow) { - _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); - } - _marketplaceWindow->setVisible(true); -} - -void PreferencesDialog::openBodyModelBrowser() { - auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars"; - auto WIDTH = 900; - auto HEIGHT = 700; - if (!_marketplaceWindow) { - _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); - } - _marketplaceWindow->setVisible(true); -} - void PreferencesDialog::openSnapshotLocationBrowser() { QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), @@ -143,12 +126,6 @@ void PreferencesDialog::loadPreferences() { _displayNameString = myAvatar->getDisplayName(); ui.displayNameEdit->setText(_displayNameString); - _faceURLString = myAvatar->getHead()->getFaceModel().getURL().toString(); - ui.faceURLEdit->setText(_faceURLString); - - _skeletonURLString = myAvatar->getSkeletonModel().getURL().toString(); - ui.skeletonURLEdit->setText(_skeletonURLString); - ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger)); ui.snapshotLocationEdit->setText(Snapshot::snapshotsLocation.get()); @@ -208,6 +185,7 @@ void PreferencesDialog::loadPreferences() { void PreferencesDialog::savePreferences() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + bool shouldDispatchIdentityPacket = false; QString displayNameStr(ui.displayNameEdit->text()); @@ -217,34 +195,6 @@ void PreferencesDialog::savePreferences() { shouldDispatchIdentityPacket = true; } - auto AVATAR_FILE_EXTENSION = ".fst"; - - QUrl faceModelURL(ui.faceURLEdit->text()); - QString faceModelURLString = faceModelURL.toString(); - if (faceModelURLString != _faceURLString) { - if (faceModelURLString.isEmpty() || faceModelURLString.toLower().contains(AVATAR_FILE_EXTENSION)) { - // change the faceModelURL in the profile, it will also update this user's BlendFace - myAvatar->setFaceModelURL(faceModelURL); - UserActivityLogger::getInstance().changedModel("head", faceModelURLString); - shouldDispatchIdentityPacket = true; - } else { - qDebug() << "ERROR: Head model not FST or blank - " << faceModelURLString; - } - } - - QUrl skeletonModelURL(ui.skeletonURLEdit->text()); - QString skeletonModelURLString = skeletonModelURL.toString(); - if (skeletonModelURLString != _skeletonURLString) { - if (skeletonModelURLString.isEmpty() || skeletonModelURLString.toLower().contains(AVATAR_FILE_EXTENSION)) { - // change the skeletonModelURL in the profile, it will also update this user's Body - myAvatar->setSkeletonModelURL(skeletonModelURL); - UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURLString); - shouldDispatchIdentityPacket = true; - } else { - qDebug() << "ERROR: Skeleton model not FST or blank - " << skeletonModelURLString; - } - } - if (shouldDispatchIdentityPacket) { myAvatar->sendIdentityPacket(); } diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 4daa2d9696..8e699c80a2 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -25,31 +25,28 @@ class PreferencesDialog : public QDialog { public: PreferencesDialog(QWidget* parent = nullptr); + void avatarDescriptionChanged(); + protected: void resizeEvent(QResizeEvent* resizeEvent); private: void loadPreferences(); void savePreferences(); - void openHeadModelBrowser(); - void openBodyModelBrowser(); Ui_PreferencesDialog ui; - QString _faceURLString; - QString _skeletonURLString; + QString _displayNameString; WebWindowClass* _marketplaceWindow = NULL; private slots: void accept(); - void setHeadUrl(QString modelUrl); - void setSkeletonUrl(QString modelUrl); void openSnapshotLocationBrowser(); void openScriptsLocationBrowser(); - void faceURLChanged(const QString& newValue); - void skeletonURLChanged(const QString& newValue); - + void headURLChanged(const QString& newValue, const QString& modelName); + void bodyURLChanged(const QString& newValue, const QString& modelName); + void fullAvatarURLChanged(const QString& newValue, const QString& modelName); }; #endif // hifi_PreferencesDialog_h diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 9ff600675f..e1fb0ce481 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -44,8 +44,9 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : connect(&_scriptsModelFilter, &QSortFilterProxyModel::modelReset, this, &RunningScriptsWidget::selectFirstInList); - QString shortcutText = Menu::getInstance()->getActionForOption(MenuOption::ReloadAllScripts)->shortcut().toString(QKeySequence::NativeText); - ui->tipLabel->setText("Tip: Use " + shortcutText + " to reload all scripts."); + // FIXME: menu isn't prepared at this point. + //QString shortcutText = Menu::getInstance()->getActionForOption(MenuOption::ReloadAllScripts)->shortcut().toString(QKeySequence::NativeText); + //ui->tipLabel->setText("Tip: Use " + shortcutText + " to reload all scripts."); _scriptsModelFilter.setSourceModel(&_scriptsModel); _scriptsModelFilter.sort(0, Qt::AscendingOrder); @@ -210,3 +211,74 @@ void RunningScriptsWidget::scriptStopped(const QString& scriptName) { void RunningScriptsWidget::allScriptsStopped() { Application::getInstance()->stopAllScripts(); } + +QVariantList RunningScriptsWidget::getRunning() { + const int WINDOWS_DRIVE_LETTER_SIZE = 1; + QVariantList result; + QStringList runningScripts = Application::getInstance()->getRunningScripts(); + for (int i = 0; i < runningScripts.size(); i++) { + QUrl runningScriptURL = QUrl(runningScripts.at(i)); + if (runningScriptURL.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { + runningScriptURL = QUrl::fromLocalFile(runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded))); + } + QVariantMap resultNode; + resultNode.insert("name", runningScriptURL.fileName()); + resultNode.insert("url", runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded))); + resultNode.insert("local", runningScriptURL.isLocalFile()); + result.append(resultNode); + } + return result; +} + +QVariantList RunningScriptsWidget::getPublic() { + return getPublicChildNodes(NULL); +} + +QVariantList RunningScriptsWidget::getPublicChildNodes(TreeNodeFolder* parent) { + QVariantList result; + QList treeNodes = Application::getInstance()->getRunningScriptsWidget()->getScriptsModel() + ->getFolderNodes(parent); + for (int i = 0; i < treeNodes.size(); i++) { + TreeNodeBase* node = treeNodes.at(i); + if (node->getType() == TREE_NODE_TYPE_FOLDER) { + TreeNodeFolder* folder = static_cast(node); + QVariantMap resultNode; + resultNode.insert("name", node->getName()); + resultNode.insert("type", "folder"); + resultNode.insert("children", getPublicChildNodes(folder)); + result.append(resultNode); + continue; + } + TreeNodeScript* script = static_cast(node); + if (script->getOrigin() == ScriptOrigin::SCRIPT_ORIGIN_LOCAL) { + continue; + } + QVariantMap resultNode; + resultNode.insert("name", node->getName()); + resultNode.insert("type", "script"); + resultNode.insert("url", script->getFullPath()); + result.append(resultNode); + } + return result; +} + +QVariantList RunningScriptsWidget::getLocal() { + QVariantList result; + QList treeNodes = Application::getInstance()->getRunningScriptsWidget()->getScriptsModel() + ->getFolderNodes(NULL); + for (int i = 0; i < treeNodes.size(); i++) { + TreeNodeBase* node = treeNodes.at(i); + if (node->getType() != TREE_NODE_TYPE_SCRIPT) { + continue; + } + TreeNodeScript* script = static_cast(node); + if (script->getOrigin() != ScriptOrigin::SCRIPT_ORIGIN_LOCAL) { + continue; + } + QVariantMap resultNode; + resultNode.insert("name", node->getName()); + resultNode.insert("path", script->getFullPath()); + result.append(resultNode); + } + return result; +} diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index 3ec5590dee..5d3f6843af 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -33,6 +33,8 @@ public: void setRunningScripts(const QStringList& list); + const ScriptsModel* getScriptsModel() { return &_scriptsModel; } + signals: void stopScriptName(const QString& name); @@ -44,7 +46,10 @@ protected: public slots: void scriptStopped(const QString& scriptName); - + QVariantList getRunning(); + QVariantList getPublic(); + QVariantList getLocal(); + private slots: void allScriptsStopped(); void updateFileFilter(const QString& filter); @@ -60,6 +65,8 @@ private: ScriptsTableWidget* _recentlyLoadedScriptsTable; QStringList _recentlyLoadedScripts; QString _lastStoppedScript; + + QVariantList getPublicChildNodes(TreeNodeFolder* parent); }; #endif // hifi_RunningScriptsWidget_h diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index a9bfbae8dd..60f985b083 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -77,7 +77,7 @@ void Cube3DOverlay::render(RenderArgs* args) { if (_drawOnHUD) { DependencyManager::get()->renderSolidCube(1.0f, glm::vec4(1.0f, 1.0f, 1.0f, alpha)); } else { - DependencyManager::get()->renderSolidCube(1.0f, glm::vec4(1.0f, 1.0f, 1.0f, alpha)); + DependencyManager::get()->renderSolidCube(1.0f, glm::vec4(1.0f, 1.0f, 1.0f, alpha)); } glPopMatrix(); @@ -89,7 +89,7 @@ void Cube3DOverlay::render(RenderArgs* args) { if (_drawOnHUD) { DependencyManager::get()->renderSolidCube(1.0f, cubeColor); } else { - DependencyManager::get()->renderSolidCube(1.0f, cubeColor); + DependencyManager::get()->renderSolidCube(1.0f, cubeColor); } glPopMatrix(); } else { diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 158deb00ff..a9eb9184dd 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -90,8 +90,8 @@ void Overlays::renderHUD() { RenderArgs args = { NULL, Application::getInstance()->getViewFrustum(), lodManager->getOctreeSizeScale(), lodManager->getBoundaryLevelAdjust(), - RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; foreach(Overlay* thisOverlay, _overlaysHUD) { if (thisOverlay->is3D()) { @@ -108,7 +108,10 @@ void Overlays::renderHUD() { } } -void Overlays::renderWorld(bool drawFront, RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) { +void Overlays::renderWorld(bool drawFront, + RenderArgs::RenderMode renderMode, + RenderArgs::RenderSide renderSide, + RenderArgs::DebugFlags renderDebugFlags) { QReadLocker lock(&_lock); if (_overlaysWorld.size() == 0) { return; @@ -125,8 +128,8 @@ void Overlays::renderWorld(bool drawFront, RenderArgs::RenderMode renderMode, Re RenderArgs args = { NULL, Application::getInstance()->getDisplayViewFrustum(), lodManager->getOctreeSizeScale(), lodManager->getBoundaryLevelAdjust(), - renderMode, renderSide, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + renderMode, renderSide, renderDebugFlags, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; foreach(Overlay* thisOverlay, _overlaysWorld) { diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 42227d04f6..04e306097b 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -54,7 +54,8 @@ public: void init(); void update(float deltatime); void renderWorld(bool drawFront, RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE, - RenderArgs::RenderSide renderSide = RenderArgs::MONO); + RenderArgs::RenderSide renderSide = RenderArgs::MONO, + RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE); void renderHUD(); public slots: diff --git a/interface/ui/avatarAppearance.ui b/interface/ui/avatarAppearance.ui new file mode 100644 index 0000000000..5ebf2c705b --- /dev/null +++ b/interface/ui/avatarAppearance.ui @@ -0,0 +1,471 @@ + + + AvatarAppearanceDialog + + + + 0 + 0 + 500 + 350 + + + + + 0 + 0 + + + + + 500 + 350 + + + + + 500 + 16777215 + + + + + 13 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + -107 + 485 + 1550 + + + + + 0 + + + 30 + + + 0 + + + 30 + + + 10 + + + + + + + 0 + + + 7 + + + 7 + + + + + + + + + Arial + + + + Use single avatar with Body and Head + + + + + + + + + + 0 + + + 10 + + + 0 + + + 0 + + + + + + + + Arial + + + + Full Avatar + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + fullAvatarURLEdit + + + + + + + + + + + + 0 + 0 + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 0 + + + + + + + + + + Arial + + + + Browse + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + Arial + + + + Use separate Body and Head avatar files + + + + + + + + + 0 + + + 10 + + + 7 + + + 7 + + + + + + + + + Arial + + + + Head + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + + + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + Arial + + + + Browse + + + + 0 + 0 + + + + + + + + + + + + + + + 0 + + + 10 + + + 7 + + + 7 + + + + + + + Arial + + + + Body + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + skeletonURLEdit + + + + + + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + Arial + + + + Browse + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + Arial + + + + Close + + + true + + + false + + + + + + + + + + + + diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index d295d094c2..d26bdaee8a 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -128,6 +128,8 @@ + + @@ -187,8 +189,9 @@ + - + 0 @@ -199,33 +202,47 @@ 7 - + Arial - Head + <html><head/><body><p>Appearance</p></body></html> Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - snapshotLocationEdit + apperanceDescription + + + - + 0 - + + + + Arial + + + + + false + + + - + Qt::Horizontal @@ -241,96 +258,14 @@ - + Arial - Browse - - - - 0 - 0 - - - - - - - - - - - - 0 - - - 7 - - - 7 - - - - - - Arial - - - - Body - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - skeletonURLEdit - - - - - - - 0 - - - - - - 0 - 0 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - Arial - - - - Browse + Change @@ -342,8 +277,11 @@ + + + @@ -413,6 +351,7 @@ + @@ -463,6 +402,10 @@ + + + + @@ -665,6 +608,7 @@ + @@ -1388,7 +1332,7 @@ - Faceshift eye detection + Faceshift eye deflection 0 diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a02dc912f7..331e62ec70 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -493,7 +493,7 @@ void AudioClient::start() { _sourceGain.initialize(); _noiseSource.initialize(); _toneSource.initialize(); - _sourceGain.setParameters(0.25f, 0.0f); + _sourceGain.setParameters(0.05f, 0.0f); _inputGain.setParameters(1.0f, 0.0f); } @@ -726,9 +726,9 @@ void AudioClient::handleAudioInput() { int inputSamplesRequired = (int)((float)AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio); QByteArray inputByteArray = _inputDevice->readAll(); - + + // Add audio source injection if enabled if (!_muted && _audioSourceInjectEnabled) { - int16_t* inputFrameData = (int16_t*)inputByteArray.data(); const uint32_t inputFrameCount = inputByteArray.size() / sizeof(int16_t); @@ -737,17 +737,12 @@ void AudioClient::handleAudioInput() { #if ENABLE_INPUT_GAIN _inputGain.render(_inputFrameBuffer); // input/mic gain+mute #endif - // Add audio source injection if enabled - if (_audioSourceInjectEnabled) { - - if (_toneSourceEnabled) { // sine generator - _toneSource.render(_inputFrameBuffer); - } - else if(_noiseSourceEnabled) { // pink noise generator - _noiseSource.render(_inputFrameBuffer); - } - _sourceGain.render(_inputFrameBuffer); // post gain + if (_toneSourceEnabled) { // sine generator + _toneSource.render(_inputFrameBuffer); + } else if(_noiseSourceEnabled) { // pink noise generator + _noiseSource.render(_inputFrameBuffer); } + _sourceGain.render(_inputFrameBuffer); // post gain _inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, true /*copy out*/); } @@ -972,8 +967,8 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { } } -void AudioClient::toggleAudioSourceInject() { - _audioSourceInjectEnabled = !_audioSourceInjectEnabled; +void AudioClient::enableAudioSourceInject(bool enable) { + _audioSourceInjectEnabled = enable; } void AudioClient::selectAudioSourcePinkNoise() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 7ac445a7fc..9d10184d13 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -1,6 +1,6 @@ // // AudioClient.h -// interface/src +// libraries/audio-client/src // // Created by Stephen Birarda on 1/22/13. // Copyright 2013 High Fidelity, Inc. @@ -108,8 +108,6 @@ public: bool isMuted() { return _muted; } - void setIsStereoInput(bool isStereoInput); - const AudioIOStats& getStats() const { return _stats; } float getInputRingBufferMsecsAvailable() const; @@ -143,17 +141,17 @@ public slots: void audioMixerKilled(); void toggleMute(); - void toggleAudioSourceInject(); - void selectAudioSourcePinkNoise(); - void selectAudioSourceSine440(); - + virtual void enableAudioSourceInject(bool enable); + virtual void selectAudioSourcePinkNoise(); + virtual void selectAudioSourceSine440(); + + virtual void setIsStereoInput(bool stereo); + void toggleAudioNoiseReduction() { _isNoiseGateEnabled = !_isNoiseGateEnabled; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } - void toggleStereoInput() { setIsStereoInput(!_isStereoInput); } - void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); void sendMuteEnvironmentPacket(); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 656d3c1f58..a5855d75d1 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -27,6 +27,12 @@ public: public slots: virtual bool outputLocalInjector(bool isStereo, qreal volume, AudioInjector* injector) = 0; + + virtual void enableAudioSourceInject(bool enable) = 0; + virtual void selectAudioSourcePinkNoise() = 0; + virtual void selectAudioSourceSine440() = 0; + + virtual void setIsStereoInput(bool stereo) = 0; }; Q_DECLARE_METATYPE(AbstractAudioInterface*) diff --git a/libraries/audio/src/AudioEffectOptions.cpp b/libraries/audio/src/AudioEffectOptions.cpp index 480779afd2..221d70aa75 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -64,7 +64,7 @@ AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) : } } -AudioEffectOptions::AudioEffectOptions(const AudioEffectOptions &other) { +AudioEffectOptions::AudioEffectOptions(const AudioEffectOptions &other) : QObject() { *this = other; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index bc49fb3514..f0e4eb118b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -58,10 +58,11 @@ AvatarData::AvatarData() : _billboard(), _errorLogExpiry(0), _owningAvatarMixer(), - _lastUpdateTimer(), _velocity(0.0f), + _targetVelocity(0.0f), _localAABox(DEFAULT_LOCAL_AABOX_CORNER, DEFAULT_LOCAL_AABOX_SCALE) { + } AvatarData::~AvatarData() { @@ -267,9 +268,6 @@ bool AvatarData::shouldLogError(const quint64& now) { // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { - // reset the last heard timer since we have new data for this AvatarData - _lastUpdateTimer.restart(); - // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); @@ -554,7 +552,17 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { } } // numJoints * 8 bytes - return sourceBuffer - startPosition; + int numBytesRead = sourceBuffer - startPosition; + _averageBytesReceived.updateAverage(numBytesRead); + return numBytesRead; +} + +int AvatarData::getAverageBytesReceivedPerSecond() const { + return lrint(_averageBytesReceived.getAverageSampleValuePerSecond()); +} + +int AvatarData::getReceiveRate() const { + return lrint(1.0f / _averageBytesReceived.getEventDeltaAverage()); } bool AvatarData::hasReferential() { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index ad90c88aaa..6a11f94cb6 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -46,9 +46,9 @@ typedef unsigned long long quint64; #include #include -#include - #include +#include +#include #include "AABox.h" #include "HandData.h" @@ -103,6 +103,12 @@ const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000; const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/models/heads/defaultAvatar_head.fst"); const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/models/skeletons/defaultAvatar_body.fst"); +const QUrl DEFAULT_FULL_AVATAR_MODEL_URL = QUrl("http://public.highfidelity.io/marketplace/contents/029db3d4-da2c-4cb2-9c08-b9612ba576f5/02949063e7c4aed42ad9d1a58461f56d.fst"); + +const QString DEFAULT_HEAD_MODEL_NAME = QString("Robot"); +const QString DEFAULT_BODY_MODEL_NAME = QString("Robot"); +const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); + // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found). // This is the start location in the Sandbox (xyz: 6270, 211, 6000). @@ -287,13 +293,16 @@ public: Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } - QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; } - const AABox& getLocalAABox() const { return _localAABox; } const Referential* getReferential() const { return _referential; } + int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); } + int getAverageBytesReceivedPerSecond() const; + int getReceiveRate() const; + void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } + glm::vec3 getTargetVelocity() const { return _targetVelocity; } public slots: void sendAvatarDataPacket(); @@ -375,7 +384,6 @@ protected: quint64 _errorLogExpiry; ///< time in future when to log an error QWeakPointer _owningAvatarMixer; - QElapsedTimer _lastUpdateTimer; PlayerPointer _player; @@ -384,9 +392,12 @@ protected: void changeReferential(Referential* ref); glm::vec3 _velocity; + glm::vec3 _targetVelocity; AABox _localAABox; + SimpleMovingAverage _averageBytesReceived; + private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index ae3a8c3e5c..b4291ef435 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "AvatarLogging.h" #include "AvatarHashMap.h" @@ -25,11 +26,11 @@ AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) return _avatarHash.erase(iterator); } -const qint64 AVATAR_SILENCE_THRESHOLD_MSECS = 5 * 1000; +const qint64 AVATAR_SILENCE_THRESHOLD_USECS = 5 * USECS_PER_SECOND; bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { return (sharedAvatar->getOwningAvatarMixer() == NULL - || sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); + || sharedAvatar->getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS); } void AvatarHashMap::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { @@ -55,6 +56,17 @@ bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) { return !avatarWithDisplayName(displayName).isNull(); } +bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { + foreach(const AvatarSharedPointer& sharedAvatar, _avatarHash) { + glm::vec3 avatarPosition = sharedAvatar->getPosition(); + float distance = glm::distance(avatarPosition, position); + if (distance < range) { + return true; + } + } + return false; +} + AvatarWeakPointer AvatarHashMap::avatarWithDisplayName(const QString& displayName) { foreach(const AvatarSharedPointer& sharedAvatar, _avatarHash) { if (sharedAvatar->getDisplayName() == displayName) { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index b59559e78c..b7d40e2acc 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -20,6 +20,7 @@ #include #include "AvatarData.h" +#include typedef QSharedPointer AvatarSharedPointer; typedef QWeakPointer AvatarWeakPointer; @@ -36,6 +37,7 @@ public: public slots: void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); bool containsAvatarWithDisplayName(const QString& displayName); + bool isAvatarInRange(const glm::vec3 & position, const float range); AvatarWeakPointer avatarWithDisplayName(const QString& displayname); private slots: diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index da972b3843..c9a968a58e 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "EntityTreeRenderer.h" @@ -30,12 +31,12 @@ #include "RenderableBoxEntityItem.h" #include "RenderableLightEntityItem.h" #include "RenderableModelEntityItem.h" +#include "RenderableParticleEffectEntityItem.h" #include "RenderableSphereEntityItem.h" #include "RenderableTextEntityItem.h" -#include "RenderableParticleEffectEntityItem.h" +#include "RenderableZoneEntityItem.h" #include "EntitiesRendererLogging.h" - EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, AbstractScriptingServicesInterface* scriptingServices) : OctreeRenderer(), @@ -56,6 +57,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Zone, RenderableZoneEntityItem::factory) _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID @@ -381,7 +383,9 @@ void EntityTreeRenderer::leaveAllEntities() { _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); } } -void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) { +void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode, + RenderArgs::RenderSide renderSide, + RenderArgs::DebugFlags renderDebugFlags) { if (_tree && !_shuttingDown) { Model::startScene(renderSide); @@ -389,14 +393,54 @@ void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode, RenderArgs::R _viewState->getShadowViewFrustum() : _viewState->getCurrentViewFrustum(); RenderArgs args = { this, frustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, renderSide, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - + renderDebugFlags, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; _tree->lockForRead(); + + // Whenever you're in an intersection between zones, we will always choose the smallest zone. + _bestZone = NULL; + _bestZoneVolume = std::numeric_limits::max(); _tree->recurseTreeWithOperation(renderOperation, &args); -// Model::RenderMode modelRenderMode = renderMode == RenderArgs::SHADOW_RENDER_MODE - // ? RenderArgs::SHADOW_RENDER_MODE : RenderArgs::DEFAULT_RENDER_MODE; + QSharedPointer scene = DependencyManager::get(); + + if (_bestZone) { + if (!_hasPreviousZone) { + _previousKeyLightColor = scene->getKeyLightColor(); + _previousKeyLightIntensity = scene->getKeyLightIntensity(); + _previousKeyLightAmbientIntensity = scene->getKeyLightAmbientIntensity(); + _previousKeyLightDirection = scene->getKeyLightDirection(); + _previousStageSunModelEnabled = scene->isStageSunModelEnabled(); + _previousStageLongitude = scene->getStageLocationLongitude(); + _previousStageLatitude = scene->getStageLocationLatitude(); + _previousStageAltitude = scene->getStageLocationAltitude(); + _previousStageHour = scene->getStageDayTime(); + _previousStageDay = scene->getStageYearTime(); + _hasPreviousZone = true; + } + scene->setKeyLightColor(_bestZone->getKeyLightColorVec3()); + scene->setKeyLightIntensity(_bestZone->getKeyLightIntensity()); + scene->setKeyLightAmbientIntensity(_bestZone->getKeyLightAmbientIntensity()); + scene->setKeyLightDirection(_bestZone->getKeyLightDirection()); + scene->setStageSunModelEnable(_bestZone->getStageSunModelEnabled()); + scene->setStageLocation(_bestZone->getStageLongitude(), _bestZone->getStageLatitude(), + _bestZone->getStageAltitude()); + scene->setStageDayTime(_bestZone->getStageHour()); + scene->setStageYearTime(_bestZone->getStageDay()); + } else { + if (_hasPreviousZone) { + scene->setKeyLightColor(_previousKeyLightColor); + scene->setKeyLightIntensity(_previousKeyLightIntensity); + scene->setKeyLightAmbientIntensity(_previousKeyLightAmbientIntensity); + scene->setKeyLightDirection(_previousKeyLightDirection); + scene->setStageSunModelEnable(_previousStageSunModelEnabled); + scene->setStageLocation(_previousStageLongitude, _previousStageLatitude, + _previousStageAltitude); + scene->setStageDayTime(_previousStageHour); + scene->setStageYearTime(_previousStageDay); + _hasPreviousZone = false; + } + } // we must call endScene while we still have the tree locked so that no one deletes a model // on us while rendering the scene @@ -454,7 +498,7 @@ const FBXGeometry* EntityTreeRenderer::getCollisionGeometryForEntity(const Entit if (entityItem->getType() == EntityTypes::Model) { const RenderableModelEntityItem* constModelEntityItem = dynamic_cast(entityItem); - if (constModelEntityItem->hasCollisionModel()) { + if (constModelEntityItem->hasCompoundShapeURL()) { RenderableModelEntityItem* modelEntityItem = const_cast(constModelEntityItem); Model* model = modelEntityItem->getModel(this); if (model) { @@ -596,34 +640,59 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) EntityItem* entityItem = entityItems[i]; if (entityItem->isVisible()) { - // render entityItem - AABox entityBox = entityItem->getAABox(); - - // TODO: some entity types (like lights) might want to be rendered even - // when they are outside of the view frustum... - float distance = args->_viewFrustum->distanceToCamera(entityBox.calcCenter()); - - bool outOfView = args->_viewFrustum->boxInFrustum(entityBox) == ViewFrustum::OUTSIDE; - if (!outOfView) { - bool bigEnoughToRender = _viewState->shouldRenderMesh(entityBox.getLargestDimension(), distance); - - if (bigEnoughToRender) { - renderProxies(entityItem, args); - Glower* glower = NULL; - if (entityItem->getGlowLevel() > 0.0f) { - glower = new Glower(entityItem->getGlowLevel()); + // NOTE: Zone Entities are a special case we handle here... Zones don't render + // like other entity types. So we will skip the normal rendering tests + if (entityItem->getType() == EntityTypes::Zone) { + if (entityItem->contains(_viewState->getAvatarPosition())) { + float entityVolumeEstimate = entityItem->getVolumeEstimate(); + if (entityVolumeEstimate < _bestZoneVolume) { + _bestZoneVolume = entityVolumeEstimate; + _bestZone = dynamic_cast(entityItem); + } else if (entityVolumeEstimate == _bestZoneVolume) { + if (!_bestZone) { + _bestZoneVolume = entityVolumeEstimate; + _bestZone = dynamic_cast(entityItem); + } else { + // in the case of the volume being equal, we will use the + // EntityItemID to deterministically pick one entity over the other + if (entityItem->getEntityItemID() < _bestZone->getEntityItemID()) { + _bestZoneVolume = entityVolumeEstimate; + _bestZone = dynamic_cast(entityItem); + } + } } - entityItem->render(args); - args->_itemsRendered++; - if (glower) { - delete glower; - } - } else { - args->_itemsTooSmall++; } } else { - args->_itemsOutOfView++; + // render entityItem + AABox entityBox = entityItem->getAABox(); + + // TODO: some entity types (like lights) might want to be rendered even + // when they are outside of the view frustum... + float distance = args->_viewFrustum->distanceToCamera(entityBox.calcCenter()); + + bool outOfView = args->_viewFrustum->boxInFrustum(entityBox) == ViewFrustum::OUTSIDE; + if (!outOfView) { + bool bigEnoughToRender = _viewState->shouldRenderMesh(entityBox.getLargestDimension(), distance); + + if (bigEnoughToRender) { + renderProxies(entityItem, args); + + Glower* glower = NULL; + if (entityItem->getGlowLevel() > 0.0f) { + glower = new Glower(entityItem->getGlowLevel()); + } + entityItem->render(args); + args->_itemsRendered++; + if (glower) { + delete glower; + } + } else { + args->_itemsTooSmall++; + } + } else { + args->_itemsOutOfView++; + } } } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index e8d70c9df9..20534c3e2b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -12,18 +12,20 @@ #ifndef hifi_EntityTreeRenderer_h #define hifi_EntityTreeRenderer_h +#include +#include + #include #include // for RayToEntityIntersectionResult #include #include #include +class AbstractScriptingServicesInterface; +class AbstractViewStateInterface; class Model; class ScriptEngine; -class AbstractViewStateInterface; -class AbstractScriptingServicesInterface; - -class ScriptEngine; +class ZoneEntityItem; class EntityScriptDetails { public: @@ -56,7 +58,8 @@ public: virtual void init(); virtual void render(RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE, - RenderArgs::RenderSide renderSide = RenderArgs::MONO); + RenderArgs::RenderSide renderSide = RenderArgs::MONO, + RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE); virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem); virtual const Model* getModelForEntityItem(const EntityItem* entityItem); @@ -164,6 +167,21 @@ private: bool _shuttingDown = false; QMultiMap _waitingOnPreload; + + bool _hasPreviousZone = false; + const ZoneEntityItem* _bestZone; + float _bestZoneVolume; + + glm::vec3 _previousKeyLightColor; + float _previousKeyLightIntensity; + float _previousKeyLightAmbientIntensity; + glm::vec3 _previousKeyLightDirection; + bool _previousStageSunModelEnabled; + float _previousStageLongitude; + float _previousStageLatitude; + float _previousStageAltitude; + float _previousStageHour; + int _previousStageDay; }; diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index f7828806ab..13df04e2e7 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -1,6 +1,6 @@ // // RenderableBoxEntityItem.cpp -// interface/src +// libraries/entities-renderer/src/ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. @@ -35,6 +35,15 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glm::vec4 cubeColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + + bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; + bool highlightSimulationOwnership = false; + if (debugSimulationOwnership) { + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + highlightSimulationOwnership = (getSimulatorID() == myNodeID); + } + glPushMatrix(); glTranslatef(position.x, position.y, position.z); glm::vec3 axis = glm::axis(rotation); @@ -43,8 +52,13 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glm::vec3 positionToCenter = center - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); glScalef(dimensions.x, dimensions.y, dimensions.z); - DependencyManager::get()->renderSolidCube(1.0f, cubeColor); + if (highlightSimulationOwnership) { + DependencyManager::get()->renderWireCube(1.0f, cubeColor); + } else { + DependencyManager::get()->renderSolidCube(1.0f, cubeColor); + } glPopMatrix(); glPopMatrix(); + RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.h b/libraries/entities-renderer/src/RenderableBoxEntityItem.h index c4495d2328..cda725056c 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.h @@ -1,6 +1,6 @@ // // RenderableBoxEntityItem.h -// interface/src/entities +// libraries/entities-renderer/src/ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. @@ -13,8 +13,9 @@ #define hifi_RenderableBoxEntityItem_h #include +#include "RenderableDebugableEntityItem.h" -class RenderableBoxEntityItem : public BoxEntityItem { +class RenderableBoxEntityItem : public BoxEntityItem { public: static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp new file mode 100644 index 0000000000..94b554b96a --- /dev/null +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -0,0 +1,55 @@ +// +// RenderableDebugableEntityItem.cpp +// libraries/entities-renderer/src/ +// +// Created by Seth Alves on 5/1/15. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + + +#include +#include +#include + +#include "RenderableDebugableEntityItem.h" + + +void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut) { + glm::vec3 position = entity->getPosition(); + glm::vec3 center = entity->getCenter(); + glm::vec3 dimensions = entity->getDimensions(); + glm::quat rotation = entity->getRotation(); + glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + if (puffedOut) { + DependencyManager::get()->renderWireCube(1.2f, greenColor); + } else { + DependencyManager::get()->renderWireCube(1.0f, greenColor); + } + glPopMatrix(); + glPopMatrix(); +} + + +void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) { + bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; + + if (debugSimulationOwnership) { + quint64 now = usecTimestampNow(); + if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { + renderBoundingBox(entity, args, true); + } + } +} diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h new file mode 100644 index 0000000000..9a8344c96d --- /dev/null +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h @@ -0,0 +1,23 @@ +// +// RenderableDebugableEntityItem.h +// libraries/entities-renderer/src/ +// +// Created by Seth Alves on 5/1/15. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderableDebugableEntityItem_h +#define hifi_RenderableDebugableEntityItem_h + +#include + +class RenderableDebugableEntityItem { +public: + static void renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut); + static void render(EntityItem* entity, RenderArgs* args); +}; + +#endif // hifi_RenderableDebugableEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 73968607f7..b3232bcb57 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -108,7 +108,6 @@ void RenderableModelEntityItem::remapTextures() { _currentTextures = _textures; } - void RenderableModelEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RMEIrender"); assert(getType() == EntityTypes::Model); @@ -117,9 +116,17 @@ void RenderableModelEntityItem::render(RenderArgs* args) { glm::vec3 position = getPosition(); glm::vec3 dimensions = getDimensions(); - float size = glm::length(dimensions); - - if (drawAsModel) { + + bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; + bool highlightSimulationOwnership = false; + if (debugSimulationOwnership) { + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + highlightSimulationOwnership = (getSimulatorID() == myNodeID); + } + + bool didDraw = false; + if (drawAsModel && !highlightSimulationOwnership) { remapTextures(); glPushMatrix(); { @@ -172,35 +179,23 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (args && (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE)) { if (movingOrAnimating) { _model->renderInScene(alpha, args); + didDraw = true; } } else { _model->renderInScene(alpha, args); + didDraw = true; } - } else { - // if we couldn't get a model, then just draw a cube - glm::vec4 color(getColor()[RED_INDEX]/255, getColor()[GREEN_INDEX]/255, getColor()[BLUE_INDEX]/255, 1.0f); - glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - DependencyManager::get()->renderWireCube(size, color); - glPopMatrix(); } - } else { - // if we couldn't get a model, then just draw a cube - glm::vec4 color(getColor()[RED_INDEX]/255, getColor()[GREEN_INDEX]/255, getColor()[BLUE_INDEX]/255, 1.0f); - glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - DependencyManager::get()->renderWireCube(size, color); - glPopMatrix(); } } glPopMatrix(); - } else { - glm::vec4 color(getColor()[RED_INDEX]/255, getColor()[GREEN_INDEX]/255, getColor()[BLUE_INDEX]/255, 1.0f); - glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - DependencyManager::get()->renderWireCube(size, color); - glPopMatrix(); } + + if (!didDraw) { + RenderableDebugableEntityItem::renderBoundingBox(this, args, false); + } + + RenderableDebugableEntityItem::render(this, args); } Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { @@ -224,10 +219,10 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { // if we have a previously allocated model, but its URL doesn't match // then we need to let our renderer update our model for us. if (_model && QUrl(getModelURL()) != _model->getURL()) { - result = _model = _myRenderer->updateModel(_model, getModelURL(), getCollisionModelURL()); + result = _model = _myRenderer->updateModel(_model, getModelURL(), getCompoundShapeURL()); _needsInitialSimulation = true; } else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one - result = _model = _myRenderer->allocateModel(getModelURL(), getCollisionModelURL()); + result = _model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL()); _needsInitialSimulation = true; } else { // we already have the model we want... result = _model; @@ -267,54 +262,56 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking); } -void RenderableModelEntityItem::setCollisionModelURL(const QString& url) { - ModelEntityItem::setCollisionModelURL(url); +void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { + ModelEntityItem::setCompoundShapeURL(url); if (_model) { _model->setCollisionModelURL(QUrl(url)); } } -bool RenderableModelEntityItem::hasCollisionModel() const { - if (_model) { - return ! _model->getCollisionURL().isEmpty(); - } else { - return !_collisionModelURL.isEmpty(); - } -} - -const QString& RenderableModelEntityItem::getCollisionModelURL() const { - // assert (!_model || _collisionModelURL == _model->getCollisionURL().toString()); - return _collisionModelURL; -} - bool RenderableModelEntityItem::isReadyToComputeShape() { + ShapeType type = getShapeType(); + if (type == SHAPE_TYPE_COMPOUND) { - if (!_model) { - return false; // hmm... + if (!_model) { + return false; // hmm... + } + + assert(!_model->getCollisionURL().isEmpty()); + + if (_model->getURL().isEmpty()) { + // we need a render geometry with a scale to proceed, so give up. + return false; + } + + const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); + const QSharedPointer renderNetworkGeometry = _model->getGeometry(); + + if ((! collisionNetworkGeometry.isNull() && collisionNetworkGeometry->isLoadedWithTextures()) && + (! renderNetworkGeometry.isNull() && renderNetworkGeometry->isLoadedWithTextures())) { + // we have both URLs AND both geometries AND they are both fully loaded. + return true; + } + + // the model is still being downloaded. + return false; } - - if (_model->getCollisionURL().isEmpty()) { - // no collision-model url, so we're ready to compute a shape (of type None). - return true; - } - - const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); - if (! collisionNetworkGeometry.isNull() && collisionNetworkGeometry->isLoadedWithTextures()) { - // we have a _collisionModelURL AND a collisionNetworkGeometry AND it's fully loaded. - return true; - } - - // the model is still being downloaded. - return false; + return true; } void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { - if (_model->getCollisionURL().isEmpty()) { - info.setParams(getShapeType(), 0.5f * getDimensions()); + ShapeType type = getShapeType(); + if (type != SHAPE_TYPE_COMPOUND) { + ModelEntityItem::computeShapeInfo(info); + info.setParams(type, 0.5f * getDimensions()); } else { const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); - const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry(); + // should never fall in here when collision model not fully loaded + // hence we assert collisionNetworkGeometry is not NULL + assert(!collisionNetworkGeometry.isNull()); + + const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry(); const QSharedPointer renderNetworkGeometry = _model->getGeometry(); const FBXGeometry& renderGeometry = renderNetworkGeometry->getFBXGeometry(); @@ -408,18 +405,17 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } glm::vec3 collisionModelDimensions = box.getDimensions(); - info.setParams(getShapeType(), collisionModelDimensions, _collisionModelURL); + info.setParams(type, collisionModelDimensions, _compoundShapeURL); info.setConvexHulls(_points); } } -ShapeType RenderableModelEntityItem::getShapeType() const { - // XXX make hull an option in edit.js ? - if (!_model || _model->getCollisionURL().isEmpty()) { - return _shapeType; - } else if (_points.size() == 1) { - return SHAPE_TYPE_CONVEX_HULL; - } else { - return SHAPE_TYPE_COMPOUND; +bool RenderableModelEntityItem::contains(const glm::vec3& point) const { + if (EntityItem::contains(point) && _model && _model->getCollisionGeometry()) { + const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); + const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry(); + return collisionGeometry.convexHullContains(worldToEntity(point)); } + + return false; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 9146a04cf8..efd60faedc 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -16,11 +16,12 @@ #include #include +#include "RenderableDebugableEntityItem.h" class Model; class EntityTreeRenderer; -class RenderableModelEntityItem : public ModelEntityItem { +class RenderableModelEntityItem : public ModelEntityItem { public: static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -52,13 +53,12 @@ public: bool needsToCallUpdate() const; - virtual void setCollisionModelURL(const QString& url); - virtual bool hasCollisionModel() const; - virtual const QString& getCollisionModelURL() const; + virtual void setCompoundShapeURL(const QString& url); bool isReadyToComputeShape(); void computeShapeInfo(ShapeInfo& info); - ShapeType getShapeType() const; + + virtual bool contains(const glm::vec3& point) const; private: void remapTextures(); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp new file mode 100644 index 0000000000..0b51cf75f2 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -0,0 +1,101 @@ +// +// RenderableZoneEntityItem.cpp +// +// +// Created by Clement on 4/22/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "RenderableZoneEntityItem.h" + +#include +#include + +EntityItem* RenderableZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new RenderableZoneEntityItem(entityID, properties); +} + +template +void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) { + QString oldShapeURL = getCompoundShapeURL(); + glm::vec3 oldPosition = getPosition(), oldDimensions = getDimensions(); + glm::quat oldRotation = getRotation(); + + setNewProperties(); + + if (oldShapeURL != getCompoundShapeURL()) { + if (!_model) { + _model = getModel(); + _needsInitialSimulation = true; + } + _model->setURL(getCompoundShapeURL(), QUrl(), true, true); + } + if (oldPosition != getPosition() || + oldRotation != getRotation() || + oldDimensions != getDimensions()) { + _needsInitialSimulation = true; + } +} + +bool RenderableZoneEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + changeProperties([&]() { + somethingChanged = this->ZoneEntityItem::setProperties(properties); + }); + return somethingChanged; +} + +int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + int bytesRead = 0; + changeProperties([&]() { + bytesRead = ZoneEntityItem::readEntitySubclassDataFromBuffer(data, bytesLeftToRead, + args, propertyFlags, overwriteLocalData); + }); + return bytesRead; +} + +Model* RenderableZoneEntityItem::getModel() { + Model* model = new Model(); + model->init(); + return model; +} + +void RenderableZoneEntityItem::initialSimulation() { + _model->setScaleToFit(true, getDimensions()); + _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + _model->simulate(0.0f); + _needsInitialSimulation = false; +} + +void RenderableZoneEntityItem::render(RenderArgs* args) { + if (_drawZoneBoundaries) { + // TODO: Draw the zone boundaries... + } +} + +bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { + if (getShapeType() != SHAPE_TYPE_COMPOUND) { + return EntityItem::contains(point); + } + + if (_model && !_model->isActive() && hasCompoundShapeURL()) { + // Since we have a delayload, we need to update the geometry if it has been downloaded + _model->setURL(getCompoundShapeURL(), QUrl(), true); + } + + if (_model && _model->isActive() && EntityItem::contains(point)) { + if (_needsInitialSimulation) { + const_cast(this)->initialSimulation(); + } + return _model->convexHullContains(point); + } + + return false; +} diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h new file mode 100644 index 0000000000..18840e274b --- /dev/null +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -0,0 +1,49 @@ +// +// RenderableZoneEntityItem.h +// +// +// Created by Clement on 4/22/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderableZoneEntityItem_h +#define hifi_RenderableZoneEntityItem_h + +#include +#include + +class NetworkGeometry; + +class RenderableZoneEntityItem : public ZoneEntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + RenderableZoneEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + ZoneEntityItem(entityItemID, properties), + _model(NULL), + _needsInitialSimulation(true) + { } + + virtual bool setProperties(const EntityItemProperties& properties); + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + virtual void render(RenderArgs* args); + virtual bool contains(const glm::vec3& point) const; + +private: + Model* getModel(); + void initialSimulation(); + + template + void changeProperties(Lambda functor); + + Model* _model; + bool _needsInitialSimulation; +}; + +#endif // hifi_RenderableZoneEntityItem_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 2fb943c0b6..7c0598fbfa 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include #include @@ -25,72 +27,54 @@ bool EntityItem::_sendPhysicsUpdates = true; -void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { - _id = entityItemID.id; - _creatorTokenID = entityItemID.creatorTokenID; - - // init values with defaults before calling setProperties +EntityItem::EntityItem(const EntityItemID& entityItemID) : + _type(EntityTypes::Unknown), + _id(entityItemID.id), + _creatorTokenID(entityItemID.creatorTokenID), + _newlyCreated(false), + _lastSimulated(0), + _lastUpdated(0), + _lastEdited(0), + _lastEditedFromRemote(0), + _lastEditedFromRemoteInRemoteTime(0), + _created(UNKNOWN_CREATED_TIME), + _changedOnServer(0), + _position(ENTITY_ITEM_ZERO_VEC3), + _dimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS), + _rotation(ENTITY_ITEM_DEFAULT_ROTATION), + _glowLevel(ENTITY_ITEM_DEFAULT_GLOW_LEVEL), + _localRenderAlpha(ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA), + _density(ENTITY_ITEM_DEFAULT_DENSITY), + _volumeMultiplier(1.0f), + _velocity(ENTITY_ITEM_DEFAULT_VELOCITY), + _gravity(ENTITY_ITEM_DEFAULT_GRAVITY), + _acceleration(ENTITY_ITEM_DEFAULT_ACCELERATION), + _damping(ENTITY_ITEM_DEFAULT_DAMPING), + _lifetime(ENTITY_ITEM_DEFAULT_LIFETIME), + _script(ENTITY_ITEM_DEFAULT_SCRIPT), + _registrationPoint(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT), + _angularVelocity(ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY), + _angularDamping(ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING), + _visible(ENTITY_ITEM_DEFAULT_VISIBLE), + _ignoreForCollisions(ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS), + _collisionsWillMove(ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE), + _locked(ENTITY_ITEM_DEFAULT_LOCKED), + _userData(ENTITY_ITEM_DEFAULT_USER_DATA), + _simulatorID(ENTITY_ITEM_DEFAULT_SIMULATOR_ID), + _simulatorIDChangedTime(0), + _marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), + _name(ENTITY_ITEM_DEFAULT_NAME), + _physicsInfo(NULL), + _dirtyFlags(0), + _element(NULL) +{ quint64 now = usecTimestampNow(); _lastSimulated = now; _lastUpdated = now; - _lastEdited = 0; - _lastEditedFromRemote = 0; - _lastEditedFromRemoteInRemoteTime = 0; - _created = UNKNOWN_CREATED_TIME; - _changedOnServer = 0; - - _position = ENTITY_ITEM_ZERO_VEC3; - _dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; - _density = ENTITY_ITEM_DEFAULT_DENSITY; - _rotation = ENTITY_ITEM_DEFAULT_ROTATION; - _glowLevel = ENTITY_ITEM_DEFAULT_GLOW_LEVEL; - _localRenderAlpha = ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA; - _velocity = ENTITY_ITEM_DEFAULT_VELOCITY; - _gravity = ENTITY_ITEM_DEFAULT_GRAVITY; - _acceleration = ENTITY_ITEM_DEFAULT_ACCELERATION; - _damping = ENTITY_ITEM_DEFAULT_DAMPING; - _lifetime = ENTITY_ITEM_DEFAULT_LIFETIME; - _script = ENTITY_ITEM_DEFAULT_SCRIPT; - _registrationPoint = ENTITY_ITEM_DEFAULT_REGISTRATION_POINT; - _angularVelocity = ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY; - _angularDamping = ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING; - _visible = ENTITY_ITEM_DEFAULT_VISIBLE; - _ignoreForCollisions = ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS; - _collisionsWillMove = ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE; - _locked = ENTITY_ITEM_DEFAULT_LOCKED; - _userData = ENTITY_ITEM_DEFAULT_USER_DATA; - _simulatorID = ENTITY_ITEM_DEFAULT_SIMULATOR_ID; - _marketplaceID = ENTITY_ITEM_DEFAULT_MARKETPLACE_ID; } -EntityItem::EntityItem(const EntityItemID& entityItemID) { - _type = EntityTypes::Unknown; - quint64 now = usecTimestampNow(); - _lastSimulated = now; - _lastUpdated = now; - _lastEdited = 0; - _lastEditedFromRemote = 0; - _lastEditedFromRemoteInRemoteTime = 0; - _created = UNKNOWN_CREATED_TIME; - _dirtyFlags = 0; - _changedOnServer = 0; - _element = NULL; - initFromEntityItemID(entityItemID); -} - -EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) { - _type = EntityTypes::Unknown; - quint64 now = usecTimestampNow(); - _lastSimulated = now; - _lastUpdated = now; - _lastEdited = 0; - _lastEditedFromRemote = 0; - _lastEditedFromRemoteInRemoteTime = 0; - _created = UNKNOWN_CREATED_TIME; - _dirtyFlags = 0; - _changedOnServer = 0; - _element = NULL; - initFromEntityItemID(entityItemID); +EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : EntityItem(entityItemID) +{ setProperties(properties); } @@ -122,6 +106,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_LOCKED; requestedProperties += PROP_USER_DATA; requestedProperties += PROP_MARKETPLACE_ID; + requestedProperties += PROP_NAME; requestedProperties += PROP_SIMULATOR_ID; return requestedProperties; @@ -248,6 +233,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, getUserData()); APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, appendValue, getSimulatorID()); APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, getMarketplaceID()); + APPEND_ENTITY_PROPERTY(PROP_NAME, appendValue, getName()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -321,6 +307,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } + // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. + glm::vec3 savePosition = _position; + glm::quat saveRotation = _rotation; + glm::vec3 saveVelocity = _velocity; + glm::vec3 saveAngularVelocity = _angularVelocity; + glm::vec3 saveGravity = _gravity; + glm::vec3 saveAcceleration = _acceleration; + + // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -420,7 +415,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef #endif bool ignoreServerPacket = false; // assume we'll use this server packet - + // If this packet is from the same server edit as the last packet we accepted from the server // we probably want to use it. if (fromSameServerEdit) { @@ -536,7 +531,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); } } - + READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation); READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity); if (useMeters) { @@ -567,13 +562,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA, setUserData); if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY_STRING(PROP_SIMULATOR_ID, setSimulatorID); + READ_ENTITY_PROPERTY_UUID(PROP_SIMULATOR_ID, setSimulatorID); } if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID); } + READ_ENTITY_PROPERTY_STRING(PROP_NAME, setName); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); //////////////////////////////////// @@ -601,11 +598,29 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef #ifdef WANT_DEBUG qCDebug(entities) << "skipTimeForward:" << skipTimeForward; #endif - simulateKinematicMotion(skipTimeForward); + + // we want to extrapolate the motion forward to compensate for packet travel time, but + // we don't want the side effect of flag setting. + simulateKinematicMotion(skipTimeForward, false); } _lastSimulated = now; } } + + + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + if (_simulatorID == myNodeID && !_simulatorID.isNull()) { + // the packet that produced this bitstream originally came from physics simulations performed by + // this node, so our version has to be newer than what the packet contained. + _position = savePosition; + _rotation = saveRotation; + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _gravity = saveGravity; + _acceleration = saveAcceleration; + } + return bytesRead; } @@ -670,6 +685,17 @@ void EntityItem::setMass(float mass) { } } +const float DEFAULT_ENTITY_RESTITUTION = 0.5f; +const float DEFAULT_ENTITY_FRICTION = 0.5f; + +float EntityItem::getRestitution() const { + return DEFAULT_ENTITY_RESTITUTION; +} + +float EntityItem::getFriction() const { + return DEFAULT_ENTITY_FRICTION; +} + void EntityItem::simulate(const quint64& now) { if (_lastSimulated == 0) { _lastSimulated = now; @@ -680,6 +706,7 @@ void EntityItem::simulate(const quint64& now) { #ifdef WANT_DEBUG qCDebug(entities) << "********** EntityItem::simulate()"; qCDebug(entities) << " entity ID=" << getEntityItemID(); + qCDebug(entities) << " simulator ID=" << getSimulatorID(); qCDebug(entities) << " now=" << now; qCDebug(entities) << " _lastSimulated=" << _lastSimulated; qCDebug(entities) << " timeElapsed=" << timeElapsed; @@ -697,6 +724,7 @@ void EntityItem::simulate(const quint64& now) { qCDebug(entities) << " MOVING...="; qCDebug(entities) << " hasVelocity=" << hasVelocity(); qCDebug(entities) << " hasGravity=" << hasGravity(); + qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); } @@ -718,7 +746,7 @@ void EntityItem::simulate(const quint64& now) { _lastSimulated = now; } -void EntityItem::simulateKinematicMotion(float timeElapsed) { +void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { if (hasAngularVelocity()) { // angular damping if (_angularDamping > 0.0f) { @@ -733,7 +761,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.0017453f; // 0.0017453 rad/sec = 0.1f degrees/sec if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) { - if (angularSpeed > 0.0f) { + if (setFlags && angularSpeed > 0.0f) { _dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE; } _angularVelocity = ENTITY_ITEM_ZERO_VEC3; @@ -766,7 +794,6 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { qCDebug(entities) << " damping:" << _damping; qCDebug(entities) << " velocity AFTER dampingResistance:" << velocity; qCDebug(entities) << " glm::length(velocity):" << glm::length(velocity); - qCDebug(entities) << " velocityEspilon :" << ENTITY_ITEM_EPSILON_VELOCITY_LENGTH; #endif } @@ -787,13 +814,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { position = newPosition; - // apply gravity - if (hasGravity()) { - // handle resting on surface case, this is definitely a bit of a hack, and it only works on the - // "ground" plane of the domain, but for now it's what we've got - velocity += getGravity() * timeElapsed; - } - + // apply effective acceleration, which will be the same as gravity if the Entity isn't at rest. if (hasAcceleration()) { velocity += getAcceleration() * timeElapsed; } @@ -802,7 +823,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f; // 1mm/sec if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) { setVelocity(ENTITY_ITEM_ZERO_VEC3); - if (speed > 0.0f) { + if (setFlags && speed > 0.0f) { _dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE; } } else { @@ -823,7 +844,27 @@ bool EntityItem::isMoving() const { return hasVelocity() || hasAngularVelocity(); } -bool EntityItem::lifetimeHasExpired() const { +glm::mat4 EntityItem::getEntityToWorldMatrix() const { + glm::mat4 translation = glm::translate(getPosition()); + glm::mat4 rotation = glm::mat4_cast(getRotation()); + glm::mat4 scale = glm::scale(getDimensions()); + glm::mat4 registration = glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); + return translation * rotation * scale * registration; +} + +glm::mat4 EntityItem::getWorldToEntityMatrix() const { + return glm::inverse(getEntityToWorldMatrix()); +} + +glm::vec3 EntityItem::entityToWorld(const glm::vec3& point) const { + return glm::vec3(getEntityToWorldMatrix() * glm::vec4(point, 1.0f)); +} + +glm::vec3 EntityItem::worldToEntity(const glm::vec3& point) const { + return glm::vec3(getWorldToEntityMatrix() * glm::vec4(point, 1.0f)); +} + +bool EntityItem::lifetimeHasExpired() const { return isMortal() && (getAge() > getLifetime()); } @@ -861,6 +902,7 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData); COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulatorID, getSimulatorID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); properties._defaultSettings = false; @@ -892,6 +934,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, setSimulatorID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); if (somethingChanged) { somethingChangedNotification(); // notify derived classes that something has changed @@ -1013,12 +1056,6 @@ AABox EntityItem::getAABox() const { return AABox(rotatedExtentsRelativeToRegistrationPoint); } -AABox EntityItem::getAABoxInDomainUnits() const { - AABox box = getAABox(); - box.scale(1.0f / (float)TREE_SCALE); - return box; -} - // NOTE: This should only be used in cases of old bitstreams which only contain radius data // 0,0,0 --> maxDimension,maxDimension,maxDimension // ... has a corner to corner distance of glm::length(maxDimension,maxDimension,maxDimension) @@ -1046,6 +1083,16 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(_dimensions); } +bool EntityItem::contains(const glm::vec3& point) const { + if (getShapeType() == SHAPE_TYPE_COMPOUND) { + return getAABox().contains(point); + } else { + ShapeInfo info; + info.setParams(getShapeType(), glm::vec3(0.5f)); + return info.contains(worldToEntity(point)); + } +} + void EntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(getShapeType(), 0.5f * getDimensions()); } @@ -1065,9 +1112,10 @@ void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) { } void EntityItem::updatePosition(const glm::vec3& value) { - if (glm::distance(_position, value) > MIN_POSITION_DELTA) { + if (value != _position) { + auto distance = glm::distance(_position, value); + _dirtyFlags |= (distance > MIN_POSITION_DELTA) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE; _position = value; - _dirtyFlags |= EntityItem::DIRTY_POSITION; } } @@ -1084,9 +1132,10 @@ void EntityItem::updateDimensions(const glm::vec3& value) { } void EntityItem::updateRotation(const glm::quat& rotation) { - if (glm::dot(_rotation, rotation) < MIN_ALIGNMENT_DOT) { - _rotation = rotation; - _dirtyFlags |= EntityItem::DIRTY_POSITION; + if (rotation != _rotation) { + auto alignmentDot = glm::abs(glm::dot(_rotation, rotation)); + _dirtyFlags |= (alignmentDot < MIN_ALIGNMENT_DOT) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE; + _rotation = rotation; } } @@ -1133,7 +1182,7 @@ void EntityItem::updateVelocity(const glm::vec3& value) { void EntityItem::updateDamping(float value) { if (fabsf(_damping - value) > MIN_DAMPING_DELTA) { _damping = glm::clamp(value, 0.0f, 1.0f); - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_MATERIAL; } } @@ -1143,7 +1192,7 @@ void EntityItem::updateGravityInDomainUnits(const glm::vec3& value) { } void EntityItem::updateGravity(const glm::vec3& value) { - if ( glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) { + if (glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) { _gravity = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } @@ -1151,26 +1200,27 @@ void EntityItem::updateGravity(const glm::vec3& value) { void EntityItem::updateAcceleration(const glm::vec3& value) { if (glm::distance(_acceleration, value) > MIN_ACCELERATION_DELTA) { - if (glm::length(value) < MIN_ACCELERATION_DELTA) { - _acceleration = ENTITY_ITEM_ZERO_VEC3; - } else { - _acceleration = value; - } + _acceleration = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { - if (glm::distance(_angularVelocity, value) > MIN_SPIN_DELTA) { - _angularVelocity = value; + auto distance = glm::distance(_angularVelocity, value); + if (distance > MIN_SPIN_DELTA) { _dirtyFlags |= EntityItem::DIRTY_VELOCITY; + if (glm::length(value) < MIN_SPIN_DELTA) { + _angularVelocity = ENTITY_ITEM_ZERO_VEC3; + } else { + _angularVelocity = value; + } } } void EntityItem::updateAngularDamping(float value) { if (fabsf(_angularDamping - value) > MIN_DAMPING_DELTA) { _angularDamping = glm::clamp(value, 0.0f, 1.0f); - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_MATERIAL; } } @@ -1195,3 +1245,9 @@ void EntityItem::updateLifetime(float value) { } } +void EntityItem::setSimulatorID(const QUuid& value) { + if (_simulatorID != value) { + _simulatorID = value; + _simulatorIDChangedTime = usecTimestampNow(); + } +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 8f7feb2ed8..6026a74e11 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -54,7 +54,9 @@ public: DIRTY_MOTION_TYPE = 0x0010, DIRTY_SHAPE = 0x0020, DIRTY_LIFETIME = 0x0040, - DIRTY_UPDATEABLE = 0x0080 + DIRTY_UPDATEABLE = 0x0080, + DIRTY_MATERIAL = 0x00100, + DIRTY_PHYSICS_NO_WAKE = 0x0200 // we want to update values in physics engine without "waking" the object up }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly @@ -132,7 +134,7 @@ public: // perform linear extrapolation for SimpleEntitySimulation void simulate(const quint64& now); - void simulateKinematicMotion(float timeElapsed); + void simulateKinematicMotion(float timeElapsed, bool setFlags=true); virtual bool needsToCallUpdate() const { return false; } @@ -145,25 +147,16 @@ public: // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } - glm::vec3 getPositionInDomainUnits() const { return _position / (float)TREE_SCALE; } /// get position in domain scale units (0.0 - 1.0) const glm::vec3& getPosition() const { return _position; } /// get position in meters - /// set position in domain scale units (0.0 - 1.0) - void setPositionInDomainUnits(const glm::vec3& value) - { setPosition(glm::clamp(value, 0.0f, 1.0f) * (float)TREE_SCALE); } void setPosition(const glm::vec3& value) { _position = value; } - glm::vec3 getCenterInDomainUnits() const { return getCenter() / (float) TREE_SCALE; } glm::vec3 getCenter() const; - glm::vec3 getDimensionsInDomainUnits() const { return _dimensions / (float)TREE_SCALE; } /// get dimensions in domain scale units (0.0 - 1.0) const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in meters - /// set dimensions in domain scale units (0.0 - 1.0) - virtual void setDimensionsInDomainUnits(const glm::vec3& value) { _dimensions = glm::abs(value) * (float)TREE_SCALE; } - /// set dimensions in meter units (0.0 - TREE_SCALE) virtual void setDimensions(const glm::vec3& value) { _dimensions = glm::abs(value); } @@ -182,25 +175,24 @@ public: float getDensity() const { return _density; } - glm::vec3 getVelocityInDomainUnits() const { return _velocity / (float)TREE_SCALE; } /// velocity in domain scale units (0.0-1.0) per second - const glm::vec3 getVelocity() const { return _velocity; } /// get velocity in meters - void setVelocityInDomainUnits(const glm::vec3& value) { _velocity = value * (float)TREE_SCALE; } /// velocity in domain scale units (0.0-1.0) per second + const glm::vec3& getVelocity() const { return _velocity; } /// get velocity in meters void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in meters bool hasVelocity() const { return _velocity != ENTITY_ITEM_ZERO_VEC3; } - glm::vec3 getGravityInDomainUnits() const { return _gravity / (float)TREE_SCALE; } /// gravity in domain scale units (0.0-1.0) per second squared const glm::vec3& getGravity() const { return _gravity; } /// get gravity in meters - void setGravityInDomainUnits(const glm::vec3& value) { _gravity = value * (float)TREE_SCALE; } /// gravity in domain scale units (0.0-1.0) per second squared void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in meters bool hasGravity() const { return _gravity != ENTITY_ITEM_ZERO_VEC3; } - const glm::vec3 getAcceleration() const { return _acceleration; } /// get acceleration in meters/second - void setAcceleration(const glm::vec3& value) { _acceleration = value; } /// acceleration in meters/second + const glm::vec3& getAcceleration() const { return _acceleration; } /// get acceleration in meters/second/second + void setAcceleration(const glm::vec3& value) { _acceleration = value; } /// acceleration in meters/second/second bool hasAcceleration() const { return _acceleration != ENTITY_ITEM_ZERO_VEC3; } float getDamping() const { return _damping; } void setDamping(float value) { _damping = value; } + float getRestitution() const; + float getFriction() const; + // lifetime related properties. float getLifetime() const { return _lifetime; } /// get the lifetime in seconds for the entity void setLifetime(float value) { _lifetime = value; } /// set the lifetime in seconds for the entity @@ -220,7 +212,6 @@ public: AACube getMaximumAACube() const; AACube getMinimumAACube() const; AABox getAABox() const; /// axis aligned bounding box in world-frame (meters) - AABox getAABoxInDomainUnits() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0) const QString& getScript() const { return _script; } void setScript(const QString& value) { _script = value; } @@ -238,6 +229,9 @@ public: float getAngularDamping() const { return _angularDamping; } void setAngularDamping(float value) { _angularDamping = value; } + QString getName() const { return _name; } + void setName(const QString& value) { _name = value; } + bool getVisible() const { return _visible; } void setVisible(bool value) { _visible = value; } bool isVisible() const { return _visible; } @@ -255,8 +249,9 @@ public: const QString& getUserData() const { return _userData; } void setUserData(const QString& value) { _userData = value; } - QString getSimulatorID() const { return _simulatorID; } - void setSimulatorID(const QString& id) { _simulatorID = id; } + QUuid getSimulatorID() const { return _simulatorID; } + void setSimulatorID(const QUuid& value); + quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } const QString& getMarketplaceID() const { return _marketplaceID; } void setMarketplaceID(const QString& value) { _marketplaceID = value; } @@ -264,11 +259,11 @@ public: // TODO: get rid of users of getRadius()... float getRadius() const; - virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } - virtual bool containsInDomainUnits(const glm::vec3& point) const { return getAABoxInDomainUnits().contains(point); } + virtual bool contains(const glm::vec3& point) const; virtual bool isReadyToComputeShape() { return true; } virtual void computeShapeInfo(ShapeInfo& info); + virtual float getVolumeEstimate() const { return _dimensions.x * _dimensions.y * _dimensions.z; } /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } @@ -308,12 +303,16 @@ public: static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; } static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; } + glm::mat4 getEntityToWorldMatrix() const; + glm::mat4 getWorldToEntityMatrix() const; + glm::vec3 worldToEntity(const glm::vec3& point) const; + glm::vec3 entityToWorld(const glm::vec3& point) const; + + quint64 getLastEditedFromRemote() { return _lastEditedFromRemote; } + protected: static bool _sendPhysicsUpdates; - - virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init - EntityTypes::EntityType _type; QUuid _id; uint32_t _creatorTokenID; @@ -323,7 +322,7 @@ protected: quint64 _lastEdited; // last official local or remote edit time quint64 _lastEditedFromRemote; // last time we received and edit from the server - quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame) + quint64 _lastEditedFromRemoteInRemoteTime; // last time we received an edit from the server (in server-time-frame) quint64 _created; quint64 _changedOnServer; @@ -351,8 +350,10 @@ protected: bool _collisionsWillMove; bool _locked; QString _userData; - QString _simulatorID; // id of Node which is currently responsible for simulating this Entity + QUuid _simulatorID; // id of Node which is currently responsible for simulating this Entity + quint64 _simulatorIDChangedTime; // when was _simulatorID last updated? QString _marketplaceID; + QString _name; // NOTE: Damping is applied like this: v *= pow(1 - damping, dt) // diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index bf2ea640f1..0a416aeaf5 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -18,14 +18,14 @@ #include #include +#include "EntitiesLogging.h" #include "EntityItem.h" #include "EntityItemProperties.h" #include "EntityItemPropertiesDefaults.h" #include "ModelEntityItem.h" -#include "TextEntityItem.h" -#include "EntitiesLogging.h" #include "ParticleEffectEntityItem.h" - +#include "TextEntityItem.h" +#include "ZoneEntityItem.h" EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1); @@ -44,7 +44,7 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(script, ENTITY_ITEM_DEFAULT_SCRIPT), CONSTRUCT_PROPERTY(color, ), CONSTRUCT_PROPERTY(modelURL, ""), - CONSTRUCT_PROPERTY(collisionModelURL, ""), + CONSTRUCT_PROPERTY(compoundShapeURL, ""), CONSTRUCT_PROPERTY(animationURL, ""), CONSTRUCT_PROPERTY(animationFPS, ModelEntityItem::DEFAULT_ANIMATION_FPS), CONSTRUCT_PROPERTY(animationFrameIndex, ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX), @@ -76,6 +76,17 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY), CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS), CONSTRUCT_PROPERTY(marketplaceID, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), + CONSTRUCT_PROPERTY(keyLightColor, ZoneEntityItem::DEFAULT_KEYLIGHT_COLOR), + CONSTRUCT_PROPERTY(keyLightIntensity, ZoneEntityItem::DEFAULT_KEYLIGHT_INTENSITY), + CONSTRUCT_PROPERTY(keyLightAmbientIntensity, ZoneEntityItem::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY), + CONSTRUCT_PROPERTY(keyLightDirection, ZoneEntityItem::DEFAULT_KEYLIGHT_DIRECTION), + CONSTRUCT_PROPERTY(stageSunModelEnabled, ZoneEntityItem::DEFAULT_STAGE_SUN_MODEL_ENABLED), + CONSTRUCT_PROPERTY(stageLatitude, ZoneEntityItem::DEFAULT_STAGE_LATITUDE), + CONSTRUCT_PROPERTY(stageLongitude, ZoneEntityItem::DEFAULT_STAGE_LONGITUDE), + CONSTRUCT_PROPERTY(stageAltitude, ZoneEntityItem::DEFAULT_STAGE_ALTITUDE), + CONSTRUCT_PROPERTY(stageDay, ZoneEntityItem::DEFAULT_STAGE_DAY), + CONSTRUCT_PROPERTY(stageHour, ZoneEntityItem::DEFAULT_STAGE_HOUR), + CONSTRUCT_PROPERTY(name, ENTITY_ITEM_DEFAULT_NAME), _id(UNKNOWN_ENTITY_ID), _idSet(false), @@ -165,7 +176,7 @@ void EntityItemProperties::debugDump() const { qCDebug(entities) << " _position=" << _position.x << "," << _position.y << "," << _position.z; qCDebug(entities) << " _dimensions=" << getDimensions(); qCDebug(entities) << " _modelURL=" << _modelURL; - qCDebug(entities) << " _collisionModelURL=" << _collisionModelURL; + qCDebug(entities) << " _compoundShapeURL=" << _compoundShapeURL; qCDebug(entities) << " changed properties..."; EntityPropertyFlags props = getChangedProperties(); props.debugDumpBits(); @@ -182,31 +193,34 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } -const char* shapeTypeNames[] = {"none", "box", "sphere", "ellipsoid", "convex-hull", "plane", "compound", "capsule-x", +const char* shapeTypeNames[] = {"none", "box", "sphere", "ellipsoid", "plane", "compound", "capsule-x", "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"}; QHash stringToShapeTypeLookup; +void addShapeType(ShapeType type) { + stringToShapeTypeLookup[shapeTypeNames[type]] = type; +} + void buildStringToShapeTypeLookup() { - stringToShapeTypeLookup["none"] = SHAPE_TYPE_NONE; - stringToShapeTypeLookup["box"] = SHAPE_TYPE_BOX; - stringToShapeTypeLookup["sphere"] = SHAPE_TYPE_SPHERE; - stringToShapeTypeLookup["ellipsoid"] = SHAPE_TYPE_ELLIPSOID; - stringToShapeTypeLookup["convex-hull"] = SHAPE_TYPE_CONVEX_HULL; - stringToShapeTypeLookup["plane"] = SHAPE_TYPE_PLANE; - stringToShapeTypeLookup["compound"] = SHAPE_TYPE_COMPOUND; - stringToShapeTypeLookup["capsule-x"] = SHAPE_TYPE_CAPSULE_X; - stringToShapeTypeLookup["capsule-y"] = SHAPE_TYPE_CAPSULE_Y; - stringToShapeTypeLookup["capsule-z"] = SHAPE_TYPE_CAPSULE_Z; - stringToShapeTypeLookup["cylinder-x"] = SHAPE_TYPE_CYLINDER_X; - stringToShapeTypeLookup["cylinder-y"] = SHAPE_TYPE_CYLINDER_Y; - stringToShapeTypeLookup["cylinder-z"] = SHAPE_TYPE_CYLINDER_Z; + addShapeType(SHAPE_TYPE_NONE); + addShapeType(SHAPE_TYPE_BOX); + addShapeType(SHAPE_TYPE_SPHERE); + addShapeType(SHAPE_TYPE_ELLIPSOID); + addShapeType(SHAPE_TYPE_PLANE); + addShapeType(SHAPE_TYPE_COMPOUND); + addShapeType(SHAPE_TYPE_CAPSULE_X); + addShapeType(SHAPE_TYPE_CAPSULE_Y); + addShapeType(SHAPE_TYPE_CAPSULE_Z); + addShapeType(SHAPE_TYPE_CYLINDER_X); + addShapeType(SHAPE_TYPE_CYLINDER_Y); + addShapeType(SHAPE_TYPE_CYLINDER_Z); } QString EntityItemProperties::getShapeTypeAsString() const { if (_shapeType < sizeof(shapeTypeNames) / sizeof(char *)) return QString(shapeTypeNames[_shapeType]); - return QString("none"); + return QString(shapeTypeNames[SHAPE_TYPE_NONE]); } void EntityItemProperties::setShapeTypeFromString(const QString& shapeName) { @@ -235,7 +249,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script); CHECK_PROPERTY_CHANGE(PROP_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); - CHECK_PROPERTY_CHANGE(PROP_COLLISION_MODEL_URL, collisionModelURL); + CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_URL, animationURL); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, animationIsPlaying); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex); @@ -268,24 +282,38 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity); CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius); CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID); + CHECK_PROPERTY_CHANGE(PROP_NAME, name); + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_COLOR, keyLightColor); + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_INTENSITY, keyLightIntensity); + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_INTENSITY, keyLightAmbientIntensity); + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, keyLightDirection); + CHECK_PROPERTY_CHANGE(PROP_STAGE_SUN_MODEL_ENABLED, stageSunModelEnabled); + CHECK_PROPERTY_CHANGE(PROP_STAGE_LATITUDE, stageLatitude); + CHECK_PROPERTY_CHANGE(PROP_STAGE_LONGITUDE, stageLongitude); + CHECK_PROPERTY_CHANGE(PROP_STAGE_ALTITUDE, stageAltitude); + CHECK_PROPERTY_CHANGE(PROP_STAGE_DAY, stageDay); + CHECK_PROPERTY_CHANGE(PROP_STAGE_HOUR, stageHour); return changedProperties; } -QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) const { +QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults) const { QScriptValue properties = engine->newObject(); + EntityItemProperties defaultEntityProperties; if (_idSet) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(id, _id.toString()); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(isKnownID, (_id != UNKNOWN_ENTITY_ID)); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(isKnownID, (_id != UNKNOWN_ENTITY_ID)); } else { - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(isKnownID, false); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(isKnownID, false); } COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(type, EntityTypes::getEntityTypeName(_type)); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(position); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(dimensions); - COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(naturalDimensions); // gettable, but not settable + if (!skipDefaults) { + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(naturalDimensions); // gettable, but not settable + } COPY_PROPERTY_TO_QSCRIPTVALUE_QUAT(rotation); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity); @@ -293,8 +321,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(damping); COPY_PROPERTY_TO_QSCRIPTVALUE(density); COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable + if (!skipDefaults) { + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable + } COPY_PROPERTY_TO_QSCRIPTVALUE(script); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(registrationPoint); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(angularVelocity); @@ -302,12 +332,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(visible); COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(color); COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL); - COPY_PROPERTY_TO_QSCRIPTVALUE(collisionModelURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(compoundShapeURL); COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL); COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying); COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS); COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(animationSettings,getAnimationSettings()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(animationSettings, getAnimationSettings()); COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions); @@ -319,7 +349,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(locked); COPY_PROPERTY_TO_QSCRIPTVALUE(textures); COPY_PROPERTY_TO_QSCRIPTVALUE(userData); - COPY_PROPERTY_TO_QSCRIPTVALUE(simulatorID); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(simulatorID, getSimulatorIDAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(text); COPY_PROPERTY_TO_QSCRIPTVALUE(lineHeight); COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(textColor, getTextColor()); @@ -333,33 +363,51 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity); COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius); COPY_PROPERTY_TO_QSCRIPTVALUE(marketplaceID); + COPY_PROPERTY_TO_QSCRIPTVALUE(name); + + COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(keyLightColor); + COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightIntensity); + COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightAmbientIntensity); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(keyLightDirection); + COPY_PROPERTY_TO_QSCRIPTVALUE(stageSunModelEnabled); + COPY_PROPERTY_TO_QSCRIPTVALUE(stageLatitude); + COPY_PROPERTY_TO_QSCRIPTVALUE(stageLongitude); + COPY_PROPERTY_TO_QSCRIPTVALUE(stageAltitude); + COPY_PROPERTY_TO_QSCRIPTVALUE(stageDay); + COPY_PROPERTY_TO_QSCRIPTVALUE(stageHour); // Sitting properties support - QScriptValue sittingPoints = engine->newObject(); - for (int i = 0; i < _sittingPoints.size(); ++i) { - QScriptValue sittingPoint = engine->newObject(); - sittingPoint.setProperty("name", _sittingPoints.at(i).name); - sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints.at(i).position)); - sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints.at(i).rotation)); - sittingPoints.setProperty(i, sittingPoint); + if (!skipDefaults) { + QScriptValue sittingPoints = engine->newObject(); + for (int i = 0; i < _sittingPoints.size(); ++i) { + QScriptValue sittingPoint = engine->newObject(); + sittingPoint.setProperty("name", _sittingPoints.at(i).name); + sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints.at(i).position)); + sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints.at(i).rotation)); + sittingPoints.setProperty(i, sittingPoint); + } + sittingPoints.setProperty("length", _sittingPoints.size()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable } - sittingPoints.setProperty("length", _sittingPoints.size()); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable - AABox aaBox = getAABox(); - QScriptValue boundingBox = engine->newObject(); - QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner()); - QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft()); - QScriptValue center = vec3toScriptValue(engine, aaBox.calcCenter()); - QScriptValue boundingBoxDimensions = vec3toScriptValue(engine, aaBox.getDimensions()); - boundingBox.setProperty("brn", bottomRightNear); - boundingBox.setProperty("tfl", topFarLeft); - boundingBox.setProperty("center", center); - boundingBox.setProperty("dimensions", boundingBoxDimensions); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(boundingBox, boundingBox); // gettable, but not settable + if (!skipDefaults) { + AABox aaBox = getAABox(); + QScriptValue boundingBox = engine->newObject(); + QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner()); + QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft()); + QScriptValue center = vec3toScriptValue(engine, aaBox.calcCenter()); + QScriptValue boundingBoxDimensions = vec3toScriptValue(engine, aaBox.getDimensions()); + boundingBox.setProperty("brn", bottomRightNear); + boundingBox.setProperty("tfl", topFarLeft); + boundingBox.setProperty("center", center); + boundingBox.setProperty("dimensions", boundingBoxDimensions); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(boundingBox, boundingBox); // gettable, but not settable + } QString textureNamesList = _textureNames.join(",\n"); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(originalTextures, textureNamesList); // gettable, but not settable + if (!skipDefaults) { + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesList); // gettable, but not settable + } return properties; } @@ -386,7 +434,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(visible, setVisible); COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(color, setColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(modelURL, setModelURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(collisionModelURL, setCollisionModelURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(compoundShapeURL, setCompoundShapeURL); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(animationURL, setAnimationURL); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(animationIsPlaying, setAnimationIsPlaying); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS); @@ -403,7 +451,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(locked, setLocked); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(textures, setTextures); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(userData, setUserData); - COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(simulatorID, setSimulatorID); + COPY_PROPERTY_FROM_QSCRIPTVALUE_UUID(simulatorID, setSimulatorID); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(text, setText); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lineHeight, setLineHeight); COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(textColor, setTextColor); @@ -417,12 +465,28 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(marketplaceID, setMarketplaceID); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(name, setName); + + COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(keyLightColor, setKeyLightColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(keyLightIntensity, setKeyLightIntensity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(keyLightAmbientIntensity, setKeyLightAmbientIntensity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(keyLightDirection, setKeyLightDirection); + COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(stageSunModelEnabled, setStageSunModelEnabled); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(stageLatitude, setStageLatitude); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(stageLongitude, setStageLongitude); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(stageAltitude, setStageAltitude); + COPY_PROPERTY_FROM_QSCRIPTVALUE_INT(stageDay, setStageDay); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(stageHour, setStageHour); _lastEdited = usecTimestampNow(); } QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties) { - return properties.copyToScriptValue(engine); + return properties.copyToScriptValue(engine, false); +} + +QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties) { + return properties.copyToScriptValue(engine, true); } void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties) { @@ -576,7 +640,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem if (properties.getType() == EntityTypes::Model) { APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, properties.getModelURL()); - APPEND_ENTITY_PROPERTY(PROP_COLLISION_MODEL_URL, appendValue, properties.getCollisionModelURL()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, appendValue, properties.getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, properties.getAnimationURL()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, properties.getAnimationFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, properties.getAnimationFrameIndex()); @@ -603,8 +667,25 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, properties.getLocalGravity()); APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius()); } + + if (properties.getType() == EntityTypes::Zone) { + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, appendColor, properties.getKeyLightColor()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, appendValue, properties.getKeyLightIntensity()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, appendValue, properties.getKeyLightAmbientIntensity()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, appendValue, properties.getKeyLightDirection()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, appendValue, properties.getStageSunModelEnabled()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, appendValue, properties.getStageLatitude()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, appendValue, properties.getStageLongitude()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, appendValue, properties.getStageAltitude()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_DAY, appendValue, properties.getStageDay()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_HOUR, appendValue, properties.getStageHour()); + + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, appendValue, (uint32_t)properties.getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, appendValue, properties.getCompoundShapeURL()); + } APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, properties.getMarketplaceID()); + APPEND_ENTITY_PROPERTY(PROP_NAME, appendValue, properties.getName()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -798,7 +879,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_USER_DATA, setUserData); - READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_SIMULATOR_ID, setSimulatorID); + READ_ENTITY_PROPERTY_UUID_TO_PROPERTIES(PROP_SIMULATOR_ID, setSimulatorID); if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_TEXT, setText); @@ -809,7 +890,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Model) { READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MODEL_URL, setModelURL); - READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_COLLISION_MODEL_URL, setCollisionModelURL); + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL); READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_URL, setAnimationURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FPS, float, setAnimationFPS); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex); @@ -836,8 +917,24 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCAL_GRAVITY, float, setLocalGravity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius); } + + if (properties.getType() == EntityTypes::Zone) { + READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_KEYLIGHT_COLOR, setKeyLightColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEYLIGHT_INTENSITY, float, setKeyLightIntensity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEYLIGHT_AMBIENT_INTENSITY, float, setKeyLightAmbientIntensity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEYLIGHT_DIRECTION, glm::vec3, setKeyLightDirection); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STAGE_SUN_MODEL_ENABLED, bool, setStageSunModelEnabled); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STAGE_LATITUDE, float, setStageLatitude); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STAGE_LONGITUDE, float, setStageLongitude); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STAGE_ALTITUDE, float, setStageAltitude); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STAGE_DAY, quint16, setStageDay); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STAGE_HOUR, float, setStageHour); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL); + } READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MARKETPLACE_ID, setMarketplaceID); + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_NAME, setName); return valid; } @@ -887,10 +984,11 @@ void EntityItemProperties::markAllChanged() { _registrationPointChanged = true; _angularVelocityChanged = true; _angularDampingChanged = true; + _nameChanged = true; _visibleChanged = true; _colorChanged = true; _modelURLChanged = true; - _collisionModelURLChanged = true; + _compoundShapeURLChanged = true; _animationURLChanged = true; _animationIsPlayingChanged = true; _animationFrameIndexChanged = true; @@ -923,6 +1021,17 @@ void EntityItemProperties::markAllChanged() { _particleRadiusChanged = true; _marketplaceIDChanged = true; + + _keyLightColorChanged = true; + _keyLightIntensityChanged = true; + _keyLightAmbientIntensityChanged = true; + _keyLightDirectionChanged = true; + _stageSunModelEnabledChanged = true; + _stageLatitudeChanged = true; + _stageLongitudeChanged = true; + _stageAltitudeChanged = true; + _stageDayChanged = true; + _stageHourChanged = true; } /// The maximum bounding cube for the entity, independent of it's rotation. diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 7de0fc0e8b..ac25f83f2b 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -93,17 +93,17 @@ enum EntityPropertyList { PROP_LOCAL_GRAVITY, PROP_PARTICLE_RADIUS, - PROP_COLLISION_MODEL_URL, + PROP_COMPOUND_SHAPE_URL, PROP_MARKETPLACE_ID, PROP_ACCELERATION, PROP_SIMULATOR_ID, - + PROP_NAME, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties ABOVE this line PROP_AFTER_LAST_ITEM, //////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////// // WARNING! Do not add props here unless you intentionally mean to reuse PROP_ indexes // @@ -113,7 +113,22 @@ enum EntityPropertyList { PROP_TEXT = PROP_MODEL_URL, PROP_LINE_HEIGHT = PROP_ANIMATION_URL, PROP_BACKGROUND_COLOR = PROP_ANIMATION_FPS, - PROP_COLLISION_MODEL_URL_OLD_VERSION = PROP_ANIMATION_FPS + 1 + PROP_COLLISION_MODEL_URL_OLD_VERSION = PROP_ANIMATION_FPS + 1, + + // Aliases/Piggyback properties for Zones. These properties intentionally reuse the enum values for + // other properties which will never overlap with each other. We do this so that we don't have to expand + // the size of the properties bitflags mask + PROP_KEYLIGHT_COLOR = PROP_COLOR, + PROP_KEYLIGHT_INTENSITY = PROP_INTENSITY, + PROP_KEYLIGHT_AMBIENT_INTENSITY = PROP_CUTOFF, + PROP_KEYLIGHT_DIRECTION = PROP_EXPONENT, + PROP_STAGE_SUN_MODEL_ENABLED = PROP_IS_SPOTLIGHT, + PROP_STAGE_LATITUDE = PROP_DIFFUSE_COLOR_UNUSED, + PROP_STAGE_LONGITUDE = PROP_AMBIENT_COLOR_UNUSED, + PROP_STAGE_ALTITUDE = PROP_SPECULAR_COLOR_UNUSED, + PROP_STAGE_DAY = PROP_LINEAR_ATTENUATION_UNUSED, + PROP_STAGE_HOUR = PROP_QUADRATIC_ATTENUATION_UNUSED, + // WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above }; @@ -137,6 +152,7 @@ class EntityItemProperties { friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); virtual ~EntityItemProperties(); @@ -144,7 +160,7 @@ public: EntityTypes::EntityType getType() const { return _type; } void setType(EntityTypes::EntityType type) { _type = type; } - virtual QScriptValue copyToScriptValue(QScriptEngine* engine) const; + virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults) const; virtual void copyFromScriptValue(const QScriptValue& object); // editing related features supported by all entities @@ -182,7 +198,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor); DEFINE_PROPERTY_REF(PROP_MODEL_URL, ModelURL, modelURL, QString); - DEFINE_PROPERTY_REF(PROP_COLLISION_MODEL_URL, CollisionModelURL, collisionModelURL, QString); + DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString); DEFINE_PROPERTY_REF(PROP_ANIMATION_URL, AnimationURL, animationURL, QString); DEFINE_PROPERTY(PROP_ANIMATION_FPS, AnimationFPS, animationFPS, float); DEFINE_PROPERTY(PROP_ANIMATION_FRAME_INDEX, AnimationFrameIndex, animationFrameIndex, float); @@ -200,7 +216,7 @@ public: DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString); DEFINE_PROPERTY_REF_WITH_SETTER_AND_GETTER(PROP_ANIMATION_SETTINGS, AnimationSettings, animationSettings, QString); DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString); - DEFINE_PROPERTY_REF(PROP_SIMULATOR_ID, SimulatorID, simulatorID, QString); + DEFINE_PROPERTY_REF(PROP_SIMULATOR_ID, SimulatorID, simulatorID, QUuid); DEFINE_PROPERTY_REF(PROP_TEXT, Text, text, QString); DEFINE_PROPERTY(PROP_LINE_HEIGHT, LineHeight, lineHeight, float); DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor); @@ -214,6 +230,18 @@ public: DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float); DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float); DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString); + DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); + DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); + DEFINE_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float); + DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); + DEFINE_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, StageSunModelEnabled, stageSunModelEnabled, bool); + DEFINE_PROPERTY(PROP_STAGE_LATITUDE, StageLatitude, stageLatitude, float); + DEFINE_PROPERTY(PROP_STAGE_LONGITUDE, StageLongitude, stageLongitude, float); + DEFINE_PROPERTY(PROP_STAGE_ALTITUDE, StageAltitude, stageAltitude, float); + DEFINE_PROPERTY(PROP_STAGE_DAY, StageDay, stageDay, quint16); + DEFINE_PROPERTY(PROP_STAGE_HOUR, StageHour, stageHour, float); + DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString); + public: float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); } @@ -257,6 +285,7 @@ public: const QStringList& getTextureNames() const { return _textureNames; } void setTextureNames(const QStringList& value) { _textureNames = value; } + QString getSimulatorIDAsString() const { return _simulatorID.toString().mid(1,36).toUpper(); } private: QUuid _id; @@ -280,6 +309,7 @@ private: }; Q_DECLARE_METATYPE(EntityItemProperties); QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); +QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties); @@ -303,6 +333,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Dimensions, dimensions, "in meters"); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Velocity, velocity, "in meters"); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Name, name, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Visible, visible, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Rotation, rotation, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Density, density, ""); @@ -313,7 +344,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Script, script, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Color, color, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ModelURL, modelURL, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, CollisionModelURL, collisionModelURL, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, CompoundShapeURL, compoundShapeURL, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AnimationURL, animationURL, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AnimationFPS, animationFPS, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AnimationFrameIndex, animationFrameIndex, ""); @@ -330,7 +361,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Textures, textures, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, UserData, userData, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulatorID, simulatorID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulatorID, simulatorID, QUuid()); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Text, text, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, LineHeight, lineHeight, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 4b595ae0b8..bdc1fb37e6 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -23,7 +23,7 @@ const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f); const bool ENTITY_ITEM_DEFAULT_LOCKED = false; const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString(""); const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString(""); -const QString ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QString(""); +const QUuid ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QUuid(); const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_GLOW_LEVEL = 0.0f; @@ -55,4 +55,6 @@ const float ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING = 0.39347f; // approx timescale const bool ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS = false; const bool ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE = false; +const QString ENTITY_ITEM_DEFAULT_NAME = QString(""); + #endif // hifi_EntityItemPropertiesDefaults_h diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index e3e54f5bc8..3fc6cfa85f 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -88,6 +88,26 @@ } \ } +#define READ_ENTITY_PROPERTY_UUID(P,O) \ + if (propertyFlags.getHasProperty(P)) { \ + uint16_t length; \ + memcpy(&length, dataAt, sizeof(length)); \ + dataAt += sizeof(length); \ + bytesRead += sizeof(length); \ + QUuid value; \ + if (length == 0) { \ + value = QUuid(); \ + } else { \ + QByteArray ba((const char*)dataAt, length); \ + value = QUuid::fromRfc4122(ba); \ + dataAt += length; \ + bytesRead += length; \ + } \ + if (overwriteLocalData) { \ + O(value); \ + } \ + } + #define READ_ENTITY_PROPERTY_COLOR(P,M) \ if (propertyFlags.getHasProperty(P)) { \ if (overwriteLocalData) { \ @@ -127,6 +147,25 @@ properties.O(value); \ } + +#define READ_ENTITY_PROPERTY_UUID_TO_PROPERTIES(P,O) \ + if (propertyFlags.getHasProperty(P)) { \ + uint16_t length; \ + memcpy(&length, dataAt, sizeof(length)); \ + dataAt += sizeof(length); \ + processedBytes += sizeof(length); \ + QUuid value; \ + if (length == 0) { \ + value = QUuid(); \ + } else { \ + QByteArray ba((const char*)dataAt, length); \ + value = QUuid::fromRfc4122(ba); \ + dataAt += length; \ + processedBytes += length; \ + } \ + properties.O(value); \ + } + #define READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(P,O) \ if (propertyFlags.getHasProperty(P)) { \ xColor color; \ @@ -159,26 +198,41 @@ #define COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(P) \ - QScriptValue P = vec3toScriptValue(engine, _##P); \ - properties.setProperty(#P, P); + if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ + QScriptValue P = vec3toScriptValue(engine, _##P); \ + properties.setProperty(#P, P); \ + } #define COPY_PROPERTY_TO_QSCRIPTVALUE_QUAT(P) \ - QScriptValue P = quatToScriptValue(engine, _##P); \ - properties.setProperty(#P, P); + if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ + QScriptValue P = quatToScriptValue(engine, _##P); \ + properties.setProperty(#P, P); \ + } #define COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(P) \ - QScriptValue P = xColorToScriptValue(engine, _##P); \ - properties.setProperty(#P, P); + if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ + QScriptValue P = xColorToScriptValue(engine, _##P); \ + properties.setProperty(#P, P); \ + } #define COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(P,G) \ - QScriptValue P = xColorToScriptValue(engine, G); \ - properties.setProperty(#P, P); + if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ + QScriptValue P = xColorToScriptValue(engine, G); \ + properties.setProperty(#P, P); \ + } -#define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(P, G) \ +#define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(P, G) \ properties.setProperty(#P, G); +#define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(P, G) \ + if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ + properties.setProperty(#P, G); \ + } + #define COPY_PROPERTY_TO_QSCRIPTVALUE(P) \ - properties.setProperty(#P, _##P); + if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ + properties.setProperty(#P, _##P); \ + } #define COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(P, S) \ QScriptValue P = object.property(#P); \ @@ -189,6 +243,15 @@ } \ } +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_INT(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + int newValue = P.toVariant().toInt(); \ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } + #define COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(P, S) \ QScriptValue P = object.property(#P); \ if (P.isValid()) { \ @@ -207,6 +270,15 @@ } \ } +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_UUID(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + QUuid newValue = P.toVariant().toUuid(); \ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } + #define COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(P, S) \ QScriptValue P = object.property(#P); \ if (P.isValid()) { \ @@ -290,6 +362,7 @@ T get##N() const { return _##n; } \ void set##N(T value) { _##n = value; _##n##Changed = true; } \ bool n##Changed() const { return _##n##Changed; } \ + void set##N##Changed(bool value) { _##n##Changed = value; } \ private: \ T _##n; \ bool _##n##Changed; @@ -299,6 +372,7 @@ const T& get##N() const { return _##n; } \ void set##N(const T& value) { _##n = value; _##n##Changed = true; } \ bool n##Changed() const { return _##n##Changed; } \ + void set##N##Changed(bool value) { _##n##Changed = value; } \ private: \ T _##n; \ bool _##n##Changed; @@ -308,6 +382,7 @@ const T& get##N() const { return _##n; } \ void set##N(const T& value); \ bool n##Changed() const; \ + void set##N##Changed(bool value) { _##n##Changed = value; } \ private: \ T _##n; \ bool _##n##Changed; @@ -317,6 +392,7 @@ T get##N() const; \ void set##N(const T& value); \ bool n##Changed() const; \ + void set##N##Changed(bool value) { _##n##Changed = value; } \ private: \ T _##n; \ bool _##n##Changed; @@ -328,6 +404,7 @@ bool n##Changed() const { return _##n##Changed; } \ QString get##N##AsString() const; \ void set##N##FromString(const QString& name); \ + void set##N##Changed(bool value) { _##n##Changed = value; } \ private: \ T _##n; \ bool _##n##Changed; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index c41b9a5c41..6632574db8 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -15,6 +15,8 @@ #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" +#include "ZoneEntityItem.h" +#include "EntitiesLogging.h" EntityScriptingInterface::EntityScriptingInterface() : @@ -60,22 +62,44 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { } + +void setSimId(EntityItemProperties& propertiesWithSimID, EntityItem* entity) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + propertiesWithSimID.setSimulatorID(myNodeID); + entity->setSimulatorID(myNodeID); +} + + + EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { // The application will keep track of creatorTokenID uint32_t creatorTokenID = EntityItemID::getNextCreatorTokenID(); - EntityItemID id(NEW_ENTITY, creatorTokenID, false ); + EntityItemProperties propertiesWithSimID = properties; + + EntityItemID id(NEW_ENTITY, creatorTokenID, false); // If we have a local entity tree set, then also update it. + bool success = true; if (_entityTree) { _entityTree->lockForWrite(); - _entityTree->addEntity(id, properties); + EntityItem* entity = _entityTree->addEntity(id, propertiesWithSimID); + if (entity) { + // This Node is creating a new object. If it's in motion, set this Node as the simulator. + setSimId(propertiesWithSimID, entity); + } else { + qCDebug(entities) << "script failed to add new Entity to local Octree"; + success = false; + } _entityTree->unlock(); } // queue the packet - queueEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + if (success) { + queueEntityMessage(PacketTypeEntityAddOrEdit, id, propertiesWithSimID); + } return id; } @@ -137,30 +161,29 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E entityID.isKnownID = true; } } - + + EntityItemProperties propertiesWithSimID = properties; + // If we have a local entity tree set, then also update it. We can do this even if we don't know // the actual id, because we can edit out local entities just with creatorTokenID if (_entityTree) { _entityTree->lockForWrite(); - _entityTree->updateEntity(entityID, properties, canAdjustLocks()); + _entityTree->updateEntity(entityID, propertiesWithSimID, canAdjustLocks()); _entityTree->unlock(); } // if at this point, we know the id, send the update to the entity server if (entityID.isKnownID) { // make sure the properties has a type, so that the encode can know which properties to include - if (properties.getType() == EntityTypes::Unknown) { + if (propertiesWithSimID.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { - EntityItemProperties tempProperties = properties; - tempProperties.setType(entity->getType()); - queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, tempProperties); - return entityID; + propertiesWithSimID.setType(entity->getType()); + setSimId(propertiesWithSimID, entity); } } - - // if the properties already includes the type, then use it as is - queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); + + queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, propertiesWithSimID); } return entityID; @@ -293,6 +316,22 @@ bool EntityScriptingInterface::getLightsArePickable() const { return LightEntityItem::getLightsArePickable(); } +void EntityScriptingInterface::setZonesArePickable(bool value) { + ZoneEntityItem::setZonesArePickable(value); +} + +bool EntityScriptingInterface::getZonesArePickable() const { + return ZoneEntityItem::getZonesArePickable(); +} + +void EntityScriptingInterface::setDrawZoneBoundaries(bool value) { + ZoneEntityItem::setDrawZoneBoundaries(value); +} + +bool EntityScriptingInterface::getDrawZoneBoundaries() const { + return ZoneEntityItem::getDrawZoneBoundaries(); +} + void EntityScriptingInterface::setSendPhysicsUpdates(bool value) { EntityItem::setSendPhysicsUpdates(value); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 075f5a712b..bde369eaf2 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -111,6 +111,12 @@ public slots: Q_INVOKABLE void setLightsArePickable(bool value); Q_INVOKABLE bool getLightsArePickable() const; + Q_INVOKABLE void setZonesArePickable(bool value); + Q_INVOKABLE bool getZonesArePickable() const; + + Q_INVOKABLE void setDrawZoneBoundaries(bool value); + Q_INVOKABLE bool getDrawZoneBoundaries() const; + Q_INVOKABLE void setSendPhysicsUpdates(bool value); Q_INVOKABLE bool getSendPhysicsUpdates() const; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ea811389cb..f8f94f8d17 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -24,6 +24,10 @@ #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" + +const quint64 SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND); + + EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), _fbxService(NULL), @@ -69,25 +73,6 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const { } } -/// Give an EntityItemID and EntityItemProperties, this will either find the correct entity that already exists -/// in the tree or it will create a new entity of the type specified by the properties and return that item. -/// In the case that it creates a new item, the item will be properly added to the tree and all appropriate lookup hashes. -EntityItem* EntityTree::getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItem* result = NULL; - - // we need to first see if we already have the entity in our tree by finding the containing element of the entity - EntityTreeElement* containingElement = getContainingElement(entityID); - if (containingElement) { - result = containingElement->getEntityWithEntityItemID(entityID); - } - - // if the element does not exist, then create a new one of the specified type... - if (!result) { - result = addEntity(entityID, properties); - } - return result; -} - /// Adds a new entity item to the tree void EntityTree::postAddEntity(EntityItem* entity) { assert(entity); @@ -127,9 +112,9 @@ bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& pr return updateEntityWithElement(entity, properties, containingElement, allowLockChange); } -bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties, +bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& origProperties, EntityTreeElement* containingElement, bool allowLockChange) { - + EntityItemProperties properties = origProperties; if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; @@ -149,6 +134,24 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro } } } else { + if (properties.simulatorIDChanged() && + !entity->getSimulatorID().isNull() && + properties.getSimulatorID() != entity->getSimulatorID()) { + // A Node is trying to take ownership of the simulation of this entity from another Node. Only allow this + // if ownership hasn't recently changed. + if (usecTimestampNow() - entity->getSimulatorIDChangedTime() < SIMULATOR_CHANGE_LOCKOUT_PERIOD) { + qCDebug(entities) << "simulator_change_lockout_period:" + << entity->getSimulatorID() << "to" << properties.getSimulatorID(); + // squash the physics-related changes. + properties.setSimulatorIDChanged(false); + properties.setPositionChanged(false); + properties.setVelocityChanged(false); + properties.setAccelerationChanged(false); + } else { + qCDebug(entities) << "allowing simulatorID change"; + } + } + QString entityScriptBefore = entity->getScript(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); @@ -189,6 +192,14 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItem* result = NULL; + if (getIsClient()) { + // if our Node isn't allowed to create entities in this domain, don't try. + auto nodeList = DependencyManager::get(); + if (!nodeList->getThisNodeCanRez()) { + return NULL; + } + } + // NOTE: This method is used in the client and the server tree. In the client, it's possible to create EntityItems // that do not yet have known IDs. In the server tree however we don't want to have entities without known IDs. bool recordCreationTime = false; @@ -518,7 +529,7 @@ bool EntityTree::findInSphereOperation(OctreeElement* element, void* extraData) // NOTE: assumes caller has handled locking void EntityTree::findEntities(const glm::vec3& center, float radius, QVector& foundEntities) { - FindAllNearPointArgs args = { center, radius }; + FindAllNearPointArgs args = { center, radius, QVector() }; // NOTE: This should use recursion, since this is a spatial operation recurseTreeWithOperation(findInSphereOperation, &args); @@ -1126,10 +1137,10 @@ bool EntityTree::sendEntitiesOperation(OctreeElement* element, void* extraData) return true; } -bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElement* element) { +bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElement* element, bool skipDefaultValues) { entityDescription["Entities"] = QVariantList(); QScriptEngine scriptEngine; - RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine); + RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues); recurseTreeWithOperator(&theOperator); return true; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index c49cfb2600..3880d67eda 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -82,7 +82,6 @@ public: virtual void update(); // The newer API... - EntityItem* getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties); void postAddEntity(EntityItem* entityItem); EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -164,7 +163,7 @@ public: bool wantEditLogging() const { return _wantEditLogging; } void setWantEditLogging(bool value) { _wantEditLogging = value; } - bool writeToMap(QVariantMap& entityDescription, OctreeElement* element); + bool writeToMap(QVariantMap& entityDescription, OctreeElement* element, bool skipDefaultValues); bool readFromMap(QVariantMap& entityDescription); signals: diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 51648c4fa6..349bd51372 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -359,7 +359,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData if (extraEncodeData && entityTreeElementExtraEncodeData) { // After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data. - // Only our patent can remove our extra data in these cases and only after it knows that all of it's + // Only our parent can remove our extra data in these cases and only after it knows that all of its // children have been encoded. // If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data // since that will signal that the entire element needs to be encoded on the next attempt @@ -696,7 +696,6 @@ bool EntityTreeElement::removeEntityItem(EntityItem* entity) { // and dirty path marking in one pass. int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { - // If we're the root, but this bitstream doesn't support root elements with data, then // return without reading any bytes if (this == _myTree->getRoot() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) { @@ -750,7 +749,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int if (bestFitBefore != bestFitAfter) { // This is the case where the entity existed, and is in some element in our tree... if (!bestFitBefore && bestFitAfter) { - // This is the case where the entity existed, and is in some element in our tree... + // This is the case where the entity existed, and is in some element in our tree... if (currentContainingElement != this) { currentContainingElement->removeEntityItem(entityItem); addEntityItem(entityItem); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 55e9512f53..f0baa3da93 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -21,9 +21,10 @@ #include "BoxEntityItem.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" +#include "ParticleEffectEntityItem.h" #include "SphereEntityItem.h" #include "TextEntityItem.h" -#include "ParticleEffectEntityItem.h" +#include "ZoneEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -39,7 +40,7 @@ REGISTER_ENTITY_TYPE(Sphere) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Text) REGISTER_ENTITY_TYPE(ParticleEffect) - +REGISTER_ENTITY_TYPE(Zone) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index e1f8e876bb..28cfe2278b 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -36,7 +36,8 @@ public: Light, Text, ParticleEffect, - LAST = ParticleEffect + Zone, + LAST = Zone } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index cdbc3d9441..1abb48abcd 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -21,7 +21,7 @@ #include "ModelEntityItem.h" const QString ModelEntityItem::DEFAULT_MODEL_URL = QString(""); -const QString ModelEntityItem::DEFAULT_COLLISION_MODEL_URL = QString(""); +const QString ModelEntityItem::DEFAULT_COMPOUND_SHAPE_URL = QString(""); const QString ModelEntityItem::DEFAULT_ANIMATION_URL = QString(""); const float ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f; const bool ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false; @@ -47,7 +47,7 @@ EntityItemProperties ModelEntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionModelURL, getCollisionModelURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationURL, getAnimationURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying); COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex); @@ -65,7 +65,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionModelURL, setCollisionModelURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationURL, setAnimationURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying); SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex); @@ -98,11 +98,11 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL); if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) { - setCollisionModelURL(""); + setCompoundShapeURL(""); } else if (args.bitstreamVersion == VERSION_ENTITIES_HAS_COLLISION_MODEL) { - READ_ENTITY_PROPERTY_STRING(PROP_COLLISION_MODEL_URL_OLD_VERSION, setCollisionModelURL); + READ_ENTITY_PROPERTY_STRING(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL); } else { - READ_ENTITY_PROPERTY_STRING(PROP_COLLISION_MODEL_URL, setCollisionModelURL); + READ_ENTITY_PROPERTY_STRING(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL); } READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL); @@ -140,7 +140,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_MODEL_URL; - requestedProperties += PROP_COLLISION_MODEL_URL; + requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_ANIMATION_URL; requestedProperties += PROP_ANIMATION_FPS; requestedProperties += PROP_ANIMATION_FRAME_INDEX; @@ -164,7 +164,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, getModelURL()); - APPEND_ENTITY_PROPERTY(PROP_COLLISION_MODEL_URL, appendValue, getCollisionModelURL()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, appendValue, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, getAnimationURL()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex()); @@ -272,20 +272,40 @@ void ModelEntityItem::debugDump() const { qCDebug(entities) << " position:" << getPosition(); qCDebug(entities) << " dimensions:" << getDimensions(); qCDebug(entities) << " model URL:" << getModelURL(); - qCDebug(entities) << " collision model URL:" << getCollisionModelURL(); + qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); } void ModelEntityItem::updateShapeType(ShapeType type) { + // BEGIN_TEMPORARY_WORKAROUND + // we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug) + // but we are now enforcing the entity properties to be consistent. To make the possible we're + // introducing a temporary workaround: we will ignore ShapeType updates that conflict with the + // _compoundShapeURL. + if (hasCompoundShapeURL()) { + type = SHAPE_TYPE_COMPOUND; + } + // END_TEMPORARY_WORKAROUND + if (type != _shapeType) { _shapeType = type; _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; } } -void ModelEntityItem::setCollisionModelURL(const QString& url) { - if (_collisionModelURL != url) { - _collisionModelURL = url; +// virtual +ShapeType ModelEntityItem::getShapeType() const { + if (_shapeType == SHAPE_TYPE_COMPOUND) { + return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; + } else { + return _shapeType; + } +} + +void ModelEntityItem::setCompoundShapeURL(const QString& url) { + if (_compoundShapeURL != url) { + _compoundShapeURL = url; _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + _shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND; } } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 9e34de445b..6fe2adc928 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -49,7 +49,7 @@ public: virtual void debugDump() const; void updateShapeType(ShapeType type); - virtual ShapeType getShapeType() const { return _shapeType; } + virtual ShapeType getShapeType() const; // TODO: Move these to subclasses, or other appropriate abstraction // getters/setters applicable to models and particles @@ -57,13 +57,13 @@ public: const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } bool hasModel() const { return !_modelURL.isEmpty(); } - virtual bool hasCollisionModel() const { return !_collisionModelURL.isEmpty(); } + virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); } static const QString DEFAULT_MODEL_URL; const QString& getModelURL() const { return _modelURL; } - static const QString DEFAULT_COLLISION_MODEL_URL; - virtual const QString& getCollisionModelURL() const { return _collisionModelURL; } + static const QString DEFAULT_COMPOUND_SHAPE_URL; + const QString& getCompoundShapeURL() const { return _compoundShapeURL; } bool hasAnimation() const { return !_animationURL.isEmpty(); } static const QString DEFAULT_ANIMATION_URL; @@ -78,7 +78,7 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; } - virtual void setCollisionModelURL(const QString& url); + virtual void setCompoundShapeURL(const QString& url); void setAnimationURL(const QString& url); static const float DEFAULT_ANIMATION_FRAME_INDEX; void setAnimationFrameIndex(float value); @@ -126,7 +126,7 @@ protected: rgbColor _color; QString _modelURL; - QString _collisionModelURL; + QString _compoundShapeURL; quint64 _lastAnimated; QString _animationURL; diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.cpp b/libraries/entities/src/RecurseOctreeToMapOperator.cpp index afe28e17e0..daa01c203e 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -12,11 +12,15 @@ #include "RecurseOctreeToMapOperator.h" -RecurseOctreeToMapOperator::RecurseOctreeToMapOperator(QVariantMap& map, OctreeElement *top, QScriptEngine *engine) : +RecurseOctreeToMapOperator::RecurseOctreeToMapOperator(QVariantMap& map, + OctreeElement *top, + QScriptEngine *engine, + bool skipDefaultValues) : RecurseOctreeOperator(), _map(map), _top(top), - _engine(engine) + _engine(engine), + _skipDefaultValues(skipDefaultValues) { // if some element "top" was given, only save information for that element and it's children. if (_top) { @@ -36,6 +40,8 @@ bool RecurseOctreeToMapOperator::preRecursion(OctreeElement* element) { bool RecurseOctreeToMapOperator::postRecursion(OctreeElement* element) { + EntityItemProperties defaultProperties; + EntityTreeElement* entityTreeElement = static_cast(element); const QList& entities = entityTreeElement->getEntities(); @@ -43,7 +49,12 @@ bool RecurseOctreeToMapOperator::postRecursion(OctreeElement* element) { foreach (EntityItem* entityItem, entities) { EntityItemProperties properties = entityItem->getProperties(); - QScriptValue qScriptValues = EntityItemPropertiesToScriptValue(_engine, properties); + QScriptValue qScriptValues; + if (_skipDefaultValues) { + qScriptValues = EntityItemNonDefaultPropertiesToScriptValue(_engine, properties); + } else { + qScriptValues = EntityItemPropertiesToScriptValue(_engine, properties); + } entitiesQList << qScriptValues.toVariant(); } _map["Entities"] = entitiesQList; diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.h b/libraries/entities/src/RecurseOctreeToMapOperator.h index 6bd44f3cbf..bfa5024b09 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.h +++ b/libraries/entities/src/RecurseOctreeToMapOperator.h @@ -13,7 +13,7 @@ class RecurseOctreeToMapOperator : public RecurseOctreeOperator { public: - RecurseOctreeToMapOperator(QVariantMap& map, OctreeElement *top, QScriptEngine *engine); + RecurseOctreeToMapOperator(QVariantMap& map, OctreeElement *top, QScriptEngine *engine, bool skipDefaultValues); bool preRecursion(OctreeElement* element); bool postRecursion(OctreeElement* element); private: @@ -21,4 +21,5 @@ public: OctreeElement *_top; QScriptEngine *_engine; bool _withinTop; + bool _skipDefaultValues; }; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 6d45768c26..6343ed3e47 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -13,8 +13,12 @@ #include "EntityItem.h" #include "SimpleEntitySimulation.h" +#include "EntitiesLogging.h" + +const quint64 AUTO_REMOVE_SIMULATION_OWNER_USEC = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { + // now is usecTimestampNow() QSet::iterator itemItr = _movingEntities.begin(); while (itemItr != _movingEntities.end()) { EntityItem* entity = *itemItr; @@ -27,6 +31,23 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { ++itemItr; } } + + // If an Entity has a simulation owner and we don't get an update for some amount of time, + // clear the owner. This guards against an interface failing to release the Entity when it + // has finished simulating it. + itemItr = _hasSimulationOwnerEntities.begin(); + while (itemItr != _hasSimulationOwnerEntities.end()) { + EntityItem* entity = *itemItr; + if (entity->getSimulatorID().isNull()) { + itemItr = _hasSimulationOwnerEntities.erase(itemItr); + } else if (usecTimestampNow() - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) { + qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); + entity->setSimulatorID(QUuid()); + itemItr = _hasSimulationOwnerEntities.erase(itemItr); + } else { + ++itemItr; + } + } } void SimpleEntitySimulation::addEntityInternal(EntityItem* entity) { @@ -35,11 +56,15 @@ void SimpleEntitySimulation::addEntityInternal(EntityItem* entity) { } else if (entity->getCollisionsWillMove()) { _movableButStoppedEntities.insert(entity); } + if (!entity->getSimulatorID().isNull()) { + _hasSimulationOwnerEntities.insert(entity); + } } void SimpleEntitySimulation::removeEntityInternal(EntityItem* entity) { _movingEntities.remove(entity); _movableButStoppedEntities.remove(entity); + _hasSimulationOwnerEntities.remove(entity); } const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_MOTION_TYPE; @@ -55,6 +80,9 @@ void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) { _movingEntities.remove(entity); _movableButStoppedEntities.remove(entity); } + if (!entity->getSimulatorID().isNull()) { + _hasSimulationOwnerEntities.insert(entity); + } } entity->clearDirtyFlags(); } @@ -62,5 +90,6 @@ void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) { void SimpleEntitySimulation::clearEntitiesInternal() { _movingEntities.clear(); _movableButStoppedEntities.clear(); + _hasSimulationOwnerEntities.clear(); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 92b6a28215..af79ec0131 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -30,6 +30,7 @@ protected: QSet _movingEntities; QSet _movableButStoppedEntities; + QSet _hasSimulationOwnerEntities; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 132ad43336..d17e49cb59 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -96,11 +96,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const { // determine the ray in the frame of the entity transformed from a unit sphere - glm::mat4 translation = glm::translate(getPosition()); - glm::mat4 rotation = glm::mat4_cast(getRotation()); - glm::mat4 scale = glm::scale(getDimensions()); - glm::mat4 registration = glm::translate(glm::vec3(0.5f, 0.5f, 0.5f) - getRegistrationPoint()); - glm::mat4 entityToWorldMatrix = translation * rotation * scale * registration; + glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); @@ -112,7 +108,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); // then translate back to work coordinates glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); - distance = glm::distance(origin,hitAt); + distance = glm::distance(origin, hitAt); return true; } return false; diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index f79a2db7ff..b6516b714f 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -50,9 +50,6 @@ public: _color[BLUE_INDEX] = value.blue; } - // TODO: implement proper contains for 3D ellipsoid - //virtual bool contains(const glm::vec3& point) const; - virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; } virtual bool supportsDetailedRayIntersection() const { return true; } diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 39a4d48d96..34d0ce051c 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -48,11 +48,6 @@ void TextEntityItem::setDimensions(const glm::vec3& value) { _dimensions = glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH); } -void TextEntityItem::setDimensionsInDomainUnits(const glm::vec3& value) { - // NOTE: Text Entities always have a "depth" of 1cm. - _dimensions = glm::vec3(value.x * (float)TREE_SCALE, value.y * (float)TREE_SCALE, TEXT_ENTITY_ITEM_FIXED_DEPTH); -} - EntityItemProperties TextEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 044975bdc8..d57b5442d6 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -24,7 +24,6 @@ public: /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately virtual void setDimensions(const glm::vec3& value); - virtual void setDimensionsInDomainUnits(const glm::vec3& value); virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } // methods for getting/setting all properties of an entity diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp new file mode 100644 index 0000000000..4a8b60a28e --- /dev/null +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -0,0 +1,226 @@ +// +// ZoneEntityItem.cpp +// libraries/entities/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include + +#include + +#include "ZoneEntityItem.h" +#include "EntityTree.h" +#include "EntitiesLogging.h" +#include "EntityTreeElement.h" + +bool ZoneEntityItem::_zonesArePickable = false; +bool ZoneEntityItem::_drawZoneBoundaries = false; + +const xColor ZoneEntityItem::DEFAULT_KEYLIGHT_COLOR = { 255, 255, 255 }; +const float ZoneEntityItem::DEFAULT_KEYLIGHT_INTENSITY = 1.0f; +const float ZoneEntityItem::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY = 0.5f; +const glm::vec3 ZoneEntityItem::DEFAULT_KEYLIGHT_DIRECTION = { 0.0f, -1.0f, 0.0f }; +const bool ZoneEntityItem::DEFAULT_STAGE_SUN_MODEL_ENABLED = false; +const float ZoneEntityItem::DEFAULT_STAGE_LATITUDE = 37.777f; +const float ZoneEntityItem::DEFAULT_STAGE_LONGITUDE = 122.407f; +const float ZoneEntityItem::DEFAULT_STAGE_ALTITUDE = 0.03f; +const quint16 ZoneEntityItem::DEFAULT_STAGE_DAY = 60; +const float ZoneEntityItem::DEFAULT_STAGE_HOUR = 12.0f; +const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX; +const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = ""; + +EntityItem* ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItem* result = new ZoneEntityItem(entityID, properties); + return result; +} + +ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + EntityItem(entityItemID) +{ + _type = EntityTypes::Zone; + _created = properties.getCreated(); + + _keyLightColor[RED_INDEX] = DEFAULT_KEYLIGHT_COLOR.red; + _keyLightColor[GREEN_INDEX] = DEFAULT_KEYLIGHT_COLOR.green; + _keyLightColor[BLUE_INDEX] = DEFAULT_KEYLIGHT_COLOR.blue; + + _keyLightIntensity = DEFAULT_KEYLIGHT_INTENSITY; + _keyLightAmbientIntensity = DEFAULT_KEYLIGHT_AMBIENT_INTENSITY; + _keyLightDirection = DEFAULT_KEYLIGHT_DIRECTION; + _stageSunModelEnabled = DEFAULT_STAGE_SUN_MODEL_ENABLED; + _stageLatitude = DEFAULT_STAGE_LATITUDE; + _stageLongitude = DEFAULT_STAGE_LONGITUDE; + _stageAltitude = DEFAULT_STAGE_ALTITUDE; + _stageDay = DEFAULT_STAGE_DAY; + _stageHour = DEFAULT_STAGE_HOUR; + _shapeType = DEFAULT_SHAPE_TYPE; + _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; + + setProperties(properties); +} + +EntityItemProperties ZoneEntityItem::getProperties() const { + EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightColor, getKeyLightColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightIntensity, getKeyLightIntensity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightAmbientIntensity, getKeyLightAmbientIntensity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightDirection, getKeyLightDirection); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(stageSunModelEnabled, getStageSunModelEnabled); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(stageLatitude, getStageLatitude); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(stageLongitude, getStageLongitude); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(stageAltitude, getStageAltitude); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(stageDay, getStageDay); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(stageHour, getStageHour); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); + + return properties; +} + +bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightColor, setKeyLightColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightIntensity, setKeyLightIntensity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightAmbientIntensity, setKeyLightAmbientIntensity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightDirection, setKeyLightDirection); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(stageSunModelEnabled, setStageSunModelEnabled); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(stageLatitude, setStageLatitude); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(stageLongitude, setStageLongitude); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(stageAltitude, setStageAltitude); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(stageDay, setStageDay); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(stageHour, setStageHour); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "ZoneEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties._lastEdited); + } + return somethingChanged; +} + +int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY_COLOR(PROP_KEYLIGHT_COLOR, _keyLightColor); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, _keyLightIntensity); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, float, _keyLightAmbientIntensity); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, _keyLightDirection); + READ_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, bool, _stageSunModelEnabled); + READ_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, float, _stageLatitude); + READ_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, float, _stageLongitude); + READ_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, float, _stageAltitude); + READ_ENTITY_PROPERTY(PROP_STAGE_DAY, quint16, _stageDay); + READ_ENTITY_PROPERTY(PROP_STAGE_HOUR, float, _stageHour); + READ_ENTITY_PROPERTY_SETTER(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY_STRING(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + requestedProperties += PROP_KEYLIGHT_COLOR; + requestedProperties += PROP_KEYLIGHT_INTENSITY; + requestedProperties += PROP_KEYLIGHT_AMBIENT_INTENSITY; + requestedProperties += PROP_KEYLIGHT_DIRECTION; + requestedProperties += PROP_STAGE_SUN_MODEL_ENABLED; + requestedProperties += PROP_STAGE_LATITUDE; + requestedProperties += PROP_STAGE_LONGITUDE; + requestedProperties += PROP_STAGE_ALTITUDE; + requestedProperties += PROP_STAGE_DAY; + requestedProperties += PROP_STAGE_HOUR; + requestedProperties += PROP_SHAPE_TYPE; + requestedProperties += PROP_COMPOUND_SHAPE_URL; + + return requestedProperties; +} + +void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, appendColor, _keyLightColor); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, appendValue, getKeyLightIntensity()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, appendValue, getKeyLightAmbientIntensity()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, appendValue, getKeyLightDirection()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, appendValue, getStageSunModelEnabled()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, appendValue, getStageLatitude()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, appendValue, getStageLongitude()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, appendValue, getStageAltitude()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_DAY, appendValue, getStageDay()); + APPEND_ENTITY_PROPERTY(PROP_STAGE_HOUR, appendValue, getStageHour()); + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, appendValue, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, appendValue, getCompoundShapeURL()); +} + +void ZoneEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qCDebug(entities) << " ZoneEntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qCDebug(entities) << " keyLightColor:" << _keyLightColor[0] << "," << _keyLightColor[1] << "," << _keyLightColor[2]; + qCDebug(entities) << " position:" << debugTreeVector(_position); + qCDebug(entities) << " dimensions:" << debugTreeVector(_dimensions); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " _keyLightIntensity:" << _keyLightIntensity; + qCDebug(entities) << " _keyLightAmbientIntensity:" << _keyLightAmbientIntensity; + qCDebug(entities) << " _keyLightDirection:" << _keyLightDirection; + qCDebug(entities) << " _stageSunModelEnabled:" << _stageSunModelEnabled; + qCDebug(entities) << " _stageLatitude:" << _stageLatitude; + qCDebug(entities) << " _stageLongitude:" << _stageLongitude; + qCDebug(entities) << " _stageAltitude:" << _stageAltitude; + qCDebug(entities) << " _stageDay:" << _stageDay; + qCDebug(entities) << " _stageHour:" << _stageHour; +} + +ShapeType ZoneEntityItem::getShapeType() const { + // Zones are not allowed to have a SHAPE_TYPE_NONE... they are always at least a SHAPE_TYPE_BOX + if (_shapeType == SHAPE_TYPE_COMPOUND) { + return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_BOX; + } else { + return _shapeType == SHAPE_TYPE_NONE ? SHAPE_TYPE_BOX : _shapeType; + } +} + +void ZoneEntityItem::setCompoundShapeURL(const QString& url) { + _compoundShapeURL = url; + if (!_compoundShapeURL.isEmpty()) { + updateShapeType(SHAPE_TYPE_COMPOUND); + } else if (_shapeType == SHAPE_TYPE_COMPOUND) { + _shapeType = DEFAULT_SHAPE_TYPE; + } +} + +bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const { + + return _zonesArePickable; +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h new file mode 100644 index 0000000000..0744abf475 --- /dev/null +++ b/libraries/entities/src/ZoneEntityItem.h @@ -0,0 +1,146 @@ +// +// ZoneEntityItem.h +// libraries/entities/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ZoneEntityItem_h +#define hifi_ZoneEntityItem_h + +#include "EntityItem.h" + +class ZoneEntityItem : public EntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + ZoneEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties() const; + virtual bool setProperties(const EntityItemProperties& properties); + + // TODO: eventually only include properties changed since the params.lastViewFrustumSent time + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + // NOTE: Apparently if you begin to return a shape type, then the physics system will prevent an avatar + // from penetrating the walls of the entity. This fact will likely be important to Clement as he works + // on better defining the shape/volume of a zone. + //virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } + + xColor getKeyLightColor() const { xColor color = { _keyLightColor[RED_INDEX], _keyLightColor[GREEN_INDEX], _keyLightColor[BLUE_INDEX] }; return color; } + void setKeyLightColor(const xColor& value) { + _keyLightColor[RED_INDEX] = value.red; + _keyLightColor[GREEN_INDEX] = value.green; + _keyLightColor[BLUE_INDEX] = value.blue; + } + + glm::vec3 getKeyLightColorVec3() const { + const quint8 MAX_COLOR = 255; + glm::vec3 color = { (float)_keyLightColor[RED_INDEX] / (float)MAX_COLOR, + (float)_keyLightColor[GREEN_INDEX] / (float)MAX_COLOR, + (float)_keyLightColor[BLUE_INDEX] / (float)MAX_COLOR }; + return color; + } + + + float getKeyLightIntensity() const { return _keyLightIntensity; } + void setKeyLightIntensity(float value) { _keyLightIntensity = value; } + + float getKeyLightAmbientIntensity() const { return _keyLightAmbientIntensity; } + void setKeyLightAmbientIntensity(float value) { _keyLightAmbientIntensity = value; } + + const glm::vec3& getKeyLightDirection() const { return _keyLightDirection; } + void setKeyLightDirection(const glm::vec3& value) { _keyLightDirection = value; } + + bool getStageSunModelEnabled() const { return _stageSunModelEnabled; } + void setStageSunModelEnabled(bool value) { _stageSunModelEnabled = value; } + + float getStageLatitude() const { return _stageLatitude; } + void setStageLatitude(float value) { _stageLatitude = value; } + + float getStageLongitude() const { return _stageLongitude; } + void setStageLongitude(float value) { _stageLongitude = value; } + + float getStageAltitude() const { return _stageAltitude; } + void setStageAltitude(float value) { _stageAltitude = value; } + + uint16_t getStageDay() const { return _stageDay; } + void setStageDay(uint16_t value) { _stageDay = value; } + + float getStageHour() const { return _stageHour; } + void setStageHour(float value) { _stageHour = value; } + + static bool getZonesArePickable() { return _zonesArePickable; } + static void setZonesArePickable(bool value) { _zonesArePickable = value; } + + static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; } + static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } + + virtual bool isReadyToComputeShape() { return false; } + void updateShapeType(ShapeType type) { _shapeType = type; } + virtual ShapeType getShapeType() const; + + virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); } + const QString getCompoundShapeURL() const { return _compoundShapeURL; } + virtual void setCompoundShapeURL(const QString& url); + + virtual bool supportsDetailedRayIntersection() const { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const; + + virtual void debugDump() const; + + static const xColor DEFAULT_KEYLIGHT_COLOR; + static const float DEFAULT_KEYLIGHT_INTENSITY; + static const float DEFAULT_KEYLIGHT_AMBIENT_INTENSITY; + static const glm::vec3 DEFAULT_KEYLIGHT_DIRECTION; + static const bool DEFAULT_STAGE_SUN_MODEL_ENABLED; + static const float DEFAULT_STAGE_LATITUDE; + static const float DEFAULT_STAGE_LONGITUDE; + static const float DEFAULT_STAGE_ALTITUDE; + static const quint16 DEFAULT_STAGE_DAY; + static const float DEFAULT_STAGE_HOUR; + static const ShapeType DEFAULT_SHAPE_TYPE; + static const QString DEFAULT_COMPOUND_SHAPE_URL; + +protected: + // properties of the "sun" in the zone + rgbColor _keyLightColor; + float _keyLightIntensity; + float _keyLightAmbientIntensity; + glm::vec3 _keyLightDirection; + bool _stageSunModelEnabled; + float _stageLatitude; + float _stageLongitude; + float _stageAltitude; + uint16_t _stageDay; + float _stageHour; + + ShapeType _shapeType = SHAPE_TYPE_NONE; + QString _compoundShapeURL; + + static bool _drawZoneBoundaries; + static bool _zonesArePickable; +}; + +#endif // hifi_ZoneEntityItem_h diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index b9b2f24034..396859f3f8 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -125,6 +125,51 @@ Extents FBXGeometry::getUnscaledMeshExtents() const { return scaledExtents; } +// TODO: Move to model::Mesh when Sam's ready +bool FBXGeometry::convexHullContains(const glm::vec3& point) const { + if (!getUnscaledMeshExtents().containsPoint(point)) { + return false; + } + + auto checkEachPrimitive = [=](FBXMesh& mesh, QVector indices, int primitiveSize) -> bool { + // Check whether the point is "behind" all the primitives. + for (int j = 0; j < indices.size(); j += primitiveSize) { + if (!isPointBehindTrianglesPlane(point, + mesh.vertices[indices[j]], + mesh.vertices[indices[j + 1]], + mesh.vertices[indices[j + 2]])) { + // it's not behind at least one so we bail + return false; + } + } + return true; + }; + + // Check that the point is contained in at least one convex mesh. + for (auto mesh : meshes) { + bool insideMesh = true; + + // To be considered inside a convex mesh, + // the point needs to be "behind" all the primitives respective planes. + for (auto part : mesh.parts) { + // run through all the triangles and quads + if (!checkEachPrimitive(mesh, part.triangleIndices, 3) || + !checkEachPrimitive(mesh, part.quadIndices, 4)) { + // If not, the point is outside, bail for this mesh + insideMesh = false; + continue; + } + } + if (insideMesh) { + // It's inside this mesh, return true. + return true; + } + } + + // It wasn't in any mesh, return false. + return false; +} + QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { if (meshIndicesToModelNames.contains(meshIndex)) { return meshIndicesToModelNames.value(meshIndex); @@ -132,8 +177,6 @@ QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { return QString(); } - - static int fbxGeometryMetaTypeId = qRegisterMetaType(); static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); @@ -1426,7 +1469,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, bool rotationMinX = false, rotationMinY = false, rotationMinZ = false; bool rotationMaxX = false, rotationMaxY = false, rotationMaxZ = false; glm::vec3 rotationMin, rotationMax; - FBXModel model = { name, -1 }; + FBXModel model = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), + glm::mat4(), glm::vec3(), glm::vec3()}; ExtractedMesh* mesh = NULL; QVector blendshapes; foreach (const FBXNode& subobject, object.children) { @@ -1642,7 +1686,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, textureContent.insert(filename, content); } } else if (object.name == "Material") { - Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(), 96.0f, 1.0f }; + Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(), 96.0f, 1.0f, + QString(""), QSharedPointer(NULL)}; foreach (const FBXNode& subobject, object.children) { bool properties = false; QByteArray propertyName; @@ -1701,11 +1746,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, material.id = getID(object.properties); material._material = model::MaterialPointer(new model::Material()); - material._material->setEmissive(material.emissive); - material._material->setDiffuse(material.diffuse); + material._material->setEmissive(material.emissive); + if (glm::all(glm::equal(material.diffuse, glm::vec3(0.0f)))) { + material._material->setDiffuse(material.diffuse); + } else { + material._material->setDiffuse(material.diffuse); + } material._material->setSpecular(material.specular); material._material->setShininess(material.shininess); - material._material->setOpacity(material.opacity); + + if (material.opacity <= 0.0f) { + material._material->setOpacity(1.0f); + } else { + material._material->setOpacity(material.opacity); + } materials.insert(material.id, material); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index d1576bc02a..648c47c975 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -203,6 +203,16 @@ public: glm::quat rotation; // relative orientation }; +inline bool operator==(const SittingPoint& lhs, const SittingPoint& rhs) +{ + return (lhs.name == rhs.name) && (lhs.position == rhs.position) && (lhs.rotation == rhs.rotation); +} + +inline bool operator!=(const SittingPoint& lhs, const SittingPoint& rhs) +{ + return (lhs.name != rhs.name) || (lhs.position != rhs.position) || (lhs.rotation != rhs.rotation); +} + /// A set of meshes extracted from an FBX document. class FBXGeometry { public: @@ -252,6 +262,7 @@ public: /// Returns the unscaled extents of the model's mesh Extents getUnscaledMeshExtents() const; + bool convexHullContains(const glm::vec3& point) const; QHash meshIndicesToModelNames; diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 6a5cc2bd53..32be82b392 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -10,6 +10,13 @@ // #include +#include +#include +#include + +#include +#include +#include #include "FSTReader.h" @@ -169,3 +176,17 @@ FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) { return ENTITY_MODEL; } + +QVariantHash FSTReader::downloadMapping(const QString& url) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(networkRequest); + qDebug() << "Downloading avatar file at " << url; + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + QByteArray fstContents = reply->readAll(); + delete reply; + return FSTReader::readMapping(fstContents); +} diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 5752a224c6..981bae4feb 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -51,6 +51,7 @@ public: static QString getNameFromType(ModelType modelType); static FSTReader::ModelType getTypeFromName(const QString& name); + static QVariantHash downloadMapping(const QString& url); private: static void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it); diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 9eb9648478..4b145b55a0 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -25,7 +25,8 @@ Batch::Batch() : _textures(), _streamFormats(), _transforms(), - _pipelines() + _pipelines(), + _framebuffers() { } @@ -41,7 +42,8 @@ void Batch::clear() { _textures.clear(); _streamFormats.clear(); _transforms.clear(); - _pipelines.clear(); + _pipelines.clear(); + _framebuffers.clear(); } uint32 Batch::cacheData(uint32 size, const void* data) { @@ -186,3 +188,10 @@ void Batch::setUniformTexture(uint32 slot, const TextureView& view) { setUniformTexture(slot, view._texture); } +void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { + ADD_COMMAND(setUniformTexture); + + _params.push_back(_framebuffers.cache(framebuffer)); + +} + diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 6e8a2d1da6..b6bc05ab40 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -23,6 +23,8 @@ #include "Pipeline.h" +#include "Framebuffer.h" + #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" class ProfileRange { @@ -112,6 +114,8 @@ public: void setUniformTexture(uint32 slot, const TexturePointer& view); void setUniformTexture(uint32 slot, const TextureView& view); // not a command, just a shortcut from a TextureView + // Framebuffer Stage + void setFramebuffer(const FramebufferPointer& framebuffer); // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long @@ -170,6 +174,8 @@ public: COMMAND_setUniformBuffer, COMMAND_setUniformTexture, + COMMAND_setFramebuffer, + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API @@ -266,6 +272,7 @@ public: typedef Cache::Vector StreamFormatCaches; typedef Cache::Vector TransformCaches; typedef Cache::Vector PipelineCaches; + typedef Cache::Vector FramebufferCaches; // Cache Data in a byte array if too big to fit in Param // FOr example Mat4s are going there @@ -289,6 +296,7 @@ public: StreamFormatCaches _streamFormats; TransformCaches _transforms; PipelineCaches _pipelines; + FramebufferCaches _framebuffers; protected: }; diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 8276ff5f95..022ca02d6d 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -15,8 +15,8 @@ #include "Resource.h" #include "Texture.h" -#include "Shader.h" #include "Pipeline.h" +#include "Framebuffer.h" namespace gpu { @@ -47,8 +47,8 @@ public: }; template< typename T > - static void setGPUObject(const Buffer& buffer, T* bo) { - buffer.setGPUObject(bo); + static void setGPUObject(const Buffer& buffer, T* object) { + buffer.setGPUObject(object); } template< typename T > static T* getGPUObject(const Buffer& buffer) { @@ -56,8 +56,8 @@ public: } template< typename T > - static void setGPUObject(const Texture& texture, T* to) { - texture.setGPUObject(to); + static void setGPUObject(const Texture& texture, T* object) { + texture.setGPUObject(object); } template< typename T > static T* getGPUObject(const Texture& texture) { @@ -65,8 +65,8 @@ public: } template< typename T > - static void setGPUObject(const Shader& shader, T* so) { - shader.setGPUObject(so); + static void setGPUObject(const Shader& shader, T* object) { + shader.setGPUObject(object); } template< typename T > static T* getGPUObject(const Shader& shader) { @@ -74,8 +74,8 @@ public: } template< typename T > - static void setGPUObject(const Pipeline& pipeline, T* po) { - pipeline.setGPUObject(po); + static void setGPUObject(const Pipeline& pipeline, T* object) { + pipeline.setGPUObject(object); } template< typename T > static T* getGPUObject(const Pipeline& pipeline) { @@ -83,14 +83,23 @@ public: } template< typename T > - static void setGPUObject(const State& state, T* so) { - state.setGPUObject(so); + static void setGPUObject(const State& state, T* object) { + state.setGPUObject(object); } template< typename T > static T* getGPUObject(const State& state) { return reinterpret_cast(state.getGPUObject()); } + template< typename T > + static void setGPUObject(const Framebuffer& framebuffer, T* object) { + framebuffer.setGPUObject(object); + } + template< typename T > + static T* getGPUObject(const Framebuffer& framebuffer) { + return reinterpret_cast(framebuffer.getGPUObject()); + } + protected: }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 7f36797374..e43c190819 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -34,6 +34,8 @@ typedef glm::mat3 Mat3; typedef glm::vec4 Vec4; typedef glm::vec3 Vec3; typedef glm::vec2 Vec2; +typedef glm::ivec2 Vec2i; +typedef glm::uvec2 Vec2u; // Description of a scalar type enum Type { @@ -118,7 +120,8 @@ enum Semantic { INDEX, //used by index buffer of a mesh PART, // used by part buffer of a mesh - DEPTH, // Depth buffer + DEPTH, // Depth only buffer + STENCIL, // Stencil only buffer DEPTH_STENCIL, // Depth Stencil buffer SRGB, @@ -171,12 +174,28 @@ public: return getRaw() != right.getRaw(); } + static const Element COLOR_RGBA_32; + protected: uint8 _semantic; uint8 _dimension : 4; uint8 _type : 4; }; + +enum ComparisonFunction { + NEVER = 0, + LESS, + EQUAL, + LESS_EQUAL, + GREATER, + NOT_EQUAL, + GREATER_EQUAL, + ALWAYS, + + NUM_COMPARISON_FUNCS, +}; + }; diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp new file mode 100755 index 0000000000..96bd3d3002 --- /dev/null +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -0,0 +1,279 @@ +// +// Framebuffer.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 4/12/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Framebuffer.h" +#include +#include + +using namespace gpu; + +Framebuffer::~Framebuffer() { +} + +Framebuffer* Framebuffer::create() { + auto framebuffer = new Framebuffer(); + framebuffer->_renderBuffers.resize(MAX_NUM_RENDER_BUFFERS); + return framebuffer; +} + + +Framebuffer* Framebuffer::create( const Format& colorBufferFormat, uint16 width, uint16 height) { + auto framebuffer = Framebuffer::create(); + + auto colorTexture = TexturePointer(Texture::create2D(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + + framebuffer->setRenderBuffer(0, colorTexture); + + return framebuffer; +} + +Framebuffer* Framebuffer::create( const Format& colorBufferFormat, const Format& depthStencilBufferFormat, uint16 width, uint16 height) { + auto framebuffer = Framebuffer::create(); + + auto colorTexture = TexturePointer(Texture::create2D(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + auto depthTexture = TexturePointer(Texture::create2D(depthStencilBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + + framebuffer->setRenderBuffer(0, colorTexture); + framebuffer->setDepthStencilBuffer(depthTexture, depthStencilBufferFormat); + + return framebuffer; +} + +Framebuffer* Framebuffer::createShadowmap(uint16 width) { + auto framebuffer = Framebuffer::create(); + auto depthTexture = TexturePointer(Texture::create2D(Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH), width, width)); + + Sampler::Desc samplerDesc; + samplerDesc._borderColor = glm::vec4(1.0f); + samplerDesc._wrapModeU = Sampler::WRAP_BORDER; + samplerDesc._wrapModeV = Sampler::WRAP_BORDER; + samplerDesc._filter = Sampler::FILTER_MIN_MAG_LINEAR; + samplerDesc._comparisonFunc = LESS_EQUAL; + + depthTexture->setSampler(Sampler(samplerDesc)); + + framebuffer->setDepthStencilBuffer(depthTexture, Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH)); + + return framebuffer; +} + +bool Framebuffer::isSwapchain() const { + return _swapchain != 0; +} + +uint32 Framebuffer::getFrameCount() const { + if (_swapchain) { + return _swapchain->getFrameCount(); + } else { + return _frameCount; + } +} + +bool Framebuffer::validateTargetCompatibility(const Texture& texture, uint32 subresource) const { + if (texture.getType() == Texture::TEX_1D) { + return false; + } + + if (isEmpty()) { + return true; + } else { + if ((texture.getWidth() == getWidth()) && + (texture.getHeight() == getHeight()) && + (texture.getNumSamples() == getNumSamples())) { + return true; + } else { + return false; + } + } +} + +void Framebuffer::updateSize(const TexturePointer& texture) { + if (!isEmpty()) { + return; + } + + if (texture) { + _width = texture->getWidth(); + _height = texture->getHeight(); + _numSamples = texture->getNumSamples(); + } else { + _width = _height = _numSamples = 0; + } +} + +void Framebuffer::resize(uint16 width, uint16 height, uint16 numSamples) { + if (width && height && numSamples && !isEmpty() && !isSwapchain()) { + if ((width != _width) || (height != _height) || (numSamples != _numSamples)) { + for (uint32 i = 0; i < _renderBuffers.size(); ++i) { + if (_renderBuffers[i]) { + _renderBuffers[i]._texture->resize2D(width, height, numSamples); + _numSamples = _renderBuffers[i]._texture->getNumSamples(); + } + } + + if (_depthStencilBuffer) { + _depthStencilBuffer._texture->resize2D(width, height, numSamples); + _numSamples = _depthStencilBuffer._texture->getNumSamples(); + } + + _width = width; + _height = height; + // _numSamples = numSamples; + } + } +} + +uint16 Framebuffer::getWidth() const { + if (isSwapchain()) { + return getSwapchain()->getWidth(); + } else { + return _width; + } +} + +uint16 Framebuffer::getHeight() const { + if (isSwapchain()) { + return getSwapchain()->getHeight(); + } else { + return _height; + } +} + +uint16 Framebuffer::getNumSamples() const { + if (isSwapchain()) { + return getSwapchain()->getNumSamples(); + } else { + return _numSamples; + } +} + +// Render buffers +int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uint32 subresource) { + if (isSwapchain()) { + return -1; + } + + // Check for the slot + if (slot >= getMaxNumRenderBuffers()) { + return -1; + } + + // Check for the compatibility of size + if (texture) { + if (!validateTargetCompatibility(*texture, subresource)) { + return -1; + } + } + + updateSize(texture); + + // assign the new one + _renderBuffers[slot] = TextureView(texture, subresource); + + // update the mask + int mask = (1<getDepthStencilBufferFormat(); + return _depthStencilBuffer._element; + } else { + return _depthStencilBuffer._element; + } +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h new file mode 100755 index 0000000000..69e507f824 --- /dev/null +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -0,0 +1,164 @@ +// +// Framebuffer.h +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 4/12/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_Framebuffer_h +#define hifi_gpu_Framebuffer_h + +#include "Texture.h" +#include + +namespace gpu { + +typedef Element Format; + +class Swapchain { +public: + // Properties + uint16 getWidth() const { return _width; } + uint16 getHeight() const { return _height; } + uint16 getNumSamples() const { return _numSamples; } + + bool hasDepthStencil() const { return _hasDepthStencil; } + bool isFullscreen() const { return _isFullscreen; } + + uint32 getSwapInterval() const { return _swapInterval; } + + bool isStereo() const { return _isStereo; } + + uint32 getFrameCount() const { return _frameCount; } + + // Pure interface + void setSwapInterval(uint32 interval); + + void resize(uint16 width, uint16 height); + void setFullscreen(bool fullscreen); + + Swapchain() {} + Swapchain(const Swapchain& swapchain) {} + virtual ~Swapchain() {} + +protected: + mutable uint32 _frameCount = 0; + + Format _colorFormat; + uint16 _width = 1; + uint16 _height = 1; + uint16 _numSamples = 1; + uint16 _swapInterval = 0; + + bool _hasDepthStencil = false; + bool _isFullscreen = false; + bool _isStereo = false; + + // Non exposed + + friend class Framebuffer; +}; +typedef std::shared_ptr SwapchainPointer; + + +class Framebuffer { +public: + enum BufferMask { + BUFFER_COLOR0 = 1, + BUFFER_COLOR1 = 2, + BUFFER_COLOR2 = 4, + BUFFER_COLOR3 = 8, + BUFFER_COLOR4 = 16, + BUFFER_COLOR5 = 32, + BUFFER_COLOR6 = 64, + BUFFER_COLOR7 = 128, + BUFFER_COLORS = 0x000000FF, + + BUFFER_DEPTH = 0x40000000, + BUFFER_STENCIL = 0x80000000, + BUFFER_DEPTHSTENCIL = 0xC0000000, + }; + + ~Framebuffer(); + + static Framebuffer* create(const SwapchainPointer& swapchain); + static Framebuffer* create(); + static Framebuffer* create(const Format& colorBufferFormat, uint16 width, uint16 height); + static Framebuffer* create(const Format& colorBufferFormat, const Format& depthStencilBufferFormat, uint16 width, uint16 height); + static Framebuffer* createShadowmap(uint16 width); + + bool isSwapchain() const; + SwapchainPointer getSwapchain() const { return _swapchain; } + + uint32 getFrameCount() const; + + // Render buffers + void removeRenderBuffers(); + uint32 getNumRenderBuffers() const; + const TextureViews& getRenderBuffers() const { return _renderBuffers; } + + int32 setRenderBuffer(uint32 slot, const TexturePointer& texture, uint32 subresource = 0); + TexturePointer getRenderBuffer(uint32 slot) const; + uint32 getRenderBufferSubresource(uint32 slot) const; + + bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); + TexturePointer getDepthStencilBuffer() const; + uint32 getDepthStencilBufferSubresource() const; + Format getDepthStencilBufferFormat() const; + + + // Properties + uint32 getBufferMask() const { return _bufferMask; } + bool isEmpty() const { return (_bufferMask == 0); } + bool hasColor() const { return (getBufferMask() & BUFFER_COLORS); } + bool hasDepthStencil() const { return (getBufferMask() & BUFFER_DEPTHSTENCIL); } + + bool validateTargetCompatibility(const Texture& texture, uint32 subresource = 0) const; + + Vec2u getSize() const { return Vec2u(getWidth(), getHeight()); } + uint16 getWidth() const; + uint16 getHeight() const; + uint16 getNumSamples() const; + + float getAspectRatio() const { return getWidth() / (float) getHeight() ; } + + // If not a swapchain canvas, resize can resize all the render buffers and depth stencil attached in one call + void resize( uint16 width, uint16 height, uint16 samples = 1 ); + + static const uint32 MAX_NUM_RENDER_BUFFERS = 8; + static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; } + +protected: + SwapchainPointer _swapchain; + + TextureViews _renderBuffers; + TextureView _depthStencilBuffer; + + uint32 _bufferMask = 0; + + uint32 _frameCount = 0; + + uint16 _width = 0; + uint16 _height = 0; + uint16 _numSamples = 0; + + void updateSize(const TexturePointer& texture); + + // Non exposed + Framebuffer(const Framebuffer& framebuffer) {} + Framebuffer() {} + + // This shouldn't be used by anything else than the Backend class with the proper casting. + mutable GPUObject* _gpuObject = NULL; + void setGPUObject(GPUObject* gpuObject) const { _gpuObject = gpuObject; } + GPUObject* getGPUObject() const { return _gpuObject; } + friend class Backend; +}; +typedef std::shared_ptr FramebufferPointer; + +} + +#endif \ No newline at end of file diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 8fcc2362c1..a898c9042b 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -32,6 +32,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::GLBackend::do_setUniformBuffer), (&::gpu::GLBackend::do_setUniformTexture), + (&::gpu::GLBackend::do_setFramebuffer), + + (&::gpu::GLBackend::do_glEnable), (&::gpu::GLBackend::do_glDisable), @@ -67,7 +70,8 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = GLBackend::GLBackend() : _input(), _transform(), - _pipeline() + _pipeline(), + _output() { initTransform(); } @@ -139,7 +143,7 @@ void GLBackend::do_draw(Batch& batch, uint32 paramOffset) { uint32 startVertex = batch._params[paramOffset + 0]._uint; glDrawArrays(mode, startVertex, numVertices); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void GLBackend::do_drawIndexed(Batch& batch, uint32 paramOffset) { @@ -155,15 +159,15 @@ void GLBackend::do_drawIndexed(Batch& batch, uint32 paramOffset) { GLenum glType = _elementTypeToGLType[_input._indexBufferType]; glDrawElements(mode, numIndices, glType, reinterpret_cast(startIndex + _input._indexBufferOffset)); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void GLBackend::do_drawInstanced(Batch& batch, uint32 paramOffset) { - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void GLBackend::do_drawIndexedInstanced(Batch& batch, uint32 paramOffset) { - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } // TODO: As long as we have gl calls explicitely issued from interface @@ -185,7 +189,7 @@ void Batch::_glEnable(GLenum cap) { } void GLBackend::do_glEnable(Batch& batch, uint32 paramOffset) { glEnable(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDisable(GLenum cap) { @@ -197,7 +201,7 @@ void Batch::_glDisable(GLenum cap) { } void GLBackend::do_glDisable(Batch& batch, uint32 paramOffset) { glDisable(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glEnableClientState(GLenum array) { @@ -209,7 +213,7 @@ void Batch::_glEnableClientState(GLenum array) { } void GLBackend::do_glEnableClientState(Batch& batch, uint32 paramOffset) { glEnableClientState(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDisableClientState(GLenum array) { @@ -221,7 +225,7 @@ void Batch::_glDisableClientState(GLenum array) { } void GLBackend::do_glDisableClientState(Batch& batch, uint32 paramOffset) { glDisableClientState(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glCullFace(GLenum mode) { @@ -233,7 +237,7 @@ void Batch::_glCullFace(GLenum mode) { } void GLBackend::do_glCullFace(Batch& batch, uint32 paramOffset) { glCullFace(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glAlphaFunc(GLenum func, GLclampf ref) { @@ -248,7 +252,7 @@ void GLBackend::do_glAlphaFunc(Batch& batch, uint32 paramOffset) { glAlphaFunc( batch._params[paramOffset + 1]._uint, batch._params[paramOffset + 0]._float); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDepthFunc(GLenum func) { @@ -260,7 +264,7 @@ void Batch::_glDepthFunc(GLenum func) { } void GLBackend::do_glDepthFunc(Batch& batch, uint32 paramOffset) { glDepthFunc(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDepthMask(GLboolean flag) { @@ -272,7 +276,7 @@ void Batch::_glDepthMask(GLboolean flag) { } void GLBackend::do_glDepthMask(Batch& batch, uint32 paramOffset) { glDepthMask(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDepthRange(GLfloat zNear, GLfloat zFar) { @@ -287,7 +291,7 @@ void GLBackend::do_glDepthRange(Batch& batch, uint32 paramOffset) { glDepthRange( batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glBindBuffer(GLenum target, GLuint buffer) { @@ -302,7 +306,7 @@ void GLBackend::do_glBindBuffer(Batch& batch, uint32 paramOffset) { glBindBuffer( batch._params[paramOffset + 1]._uint, batch._params[paramOffset + 0]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glBindTexture(GLenum target, GLuint texture) { @@ -317,7 +321,7 @@ void GLBackend::do_glBindTexture(Batch& batch, uint32 paramOffset) { glBindTexture( batch._params[paramOffset + 1]._uint, batch._params[paramOffset + 0]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glActiveTexture(GLenum texture) { @@ -329,7 +333,7 @@ void Batch::_glActiveTexture(GLenum texture) { } void GLBackend::do_glActiveTexture(Batch& batch, uint32 paramOffset) { glActiveTexture(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDrawBuffers(GLsizei n, const GLenum* bufs) { @@ -344,7 +348,7 @@ void GLBackend::do_glDrawBuffers(Batch& batch, uint32 paramOffset) { glDrawBuffers( batch._params[paramOffset + 1]._uint, (const GLenum*)batch.editData(batch._params[paramOffset + 0]._uint)); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glUseProgram(GLuint program) { @@ -361,7 +365,7 @@ void GLBackend::do_glUseProgram(Batch& batch, uint32 paramOffset) { _pipeline._invalidProgram = false; glUseProgram(_pipeline._program); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glUniform1f(GLint location, GLfloat v0) { @@ -381,7 +385,7 @@ void GLBackend::do_glUniform1f(Batch& batch, uint32 paramOffset) { glUniform1f( batch._params[paramOffset + 1]._int, batch._params[paramOffset + 0]._float); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glUniform2f(GLint location, GLfloat v0, GLfloat v1) { @@ -398,7 +402,7 @@ void GLBackend::do_glUniform2f(Batch& batch, uint32 paramOffset) { batch._params[paramOffset + 2]._int, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glUniform4fv(GLint location, GLsizei count, const GLfloat* value) { @@ -417,7 +421,7 @@ void GLBackend::do_glUniform4fv(Batch& batch, uint32 paramOffset) { batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) { @@ -437,7 +441,7 @@ void GLBackend::do_glUniformMatrix4fv(Batch& batch, uint32 paramOffset) { batch._params[paramOffset + 2]._uint, batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glEnableVertexAttribArray(GLint location) { @@ -449,7 +453,7 @@ void Batch::_glEnableVertexAttribArray(GLint location) { } void GLBackend::do_glEnableVertexAttribArray(Batch& batch, uint32 paramOffset) { glEnableVertexAttribArray(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glDisableVertexAttribArray(GLint location) { @@ -461,7 +465,7 @@ void Batch::_glDisableVertexAttribArray(GLint location) { } void GLBackend::do_glDisableVertexAttribArray(Batch& batch, uint32 paramOffset) { glDisableVertexAttribArray(batch._params[paramOffset]._uint); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void Batch::_glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { @@ -480,6 +484,6 @@ void GLBackend::do_glColor4f(Batch& batch, uint32 paramOffset) { batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index ea97fd8908..b725fa46d0 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -53,6 +53,7 @@ public: Stamp _storageStamp; Stamp _contentStamp; GLuint _texture; + GLenum _target; GLuint _size; GLTexture(); @@ -61,6 +62,9 @@ public: static GLTexture* syncGPUObject(const Texture& texture); static GLuint getTextureID(const TexturePointer& texture); + // very specific for now + static void syncSampler(const Sampler& sampler, Texture::Type type, GLTexture* object); + class GLShader : public GPUObject { public: GLuint _shader; @@ -133,14 +137,25 @@ public: class GLPipeline : public GPUObject { public: - GLShader* _program; - GLState* _state; + GLShader* _program = 0; + GLState* _state = 0; GLPipeline(); ~GLPipeline(); }; static GLPipeline* syncGPUObject(const Pipeline& pipeline); + + class GLFramebuffer : public GPUObject { + public: + GLuint _fbo = 0; + + GLFramebuffer(); + ~GLFramebuffer(); + }; + static GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer); + static GLuint getFramebufferID(const FramebufferPointer& framebuffer); + static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; static const int MAX_NUM_INPUT_BUFFERS = 16; @@ -276,8 +291,8 @@ protected: State::Signature _stateSignatureCache; GLState* _state; - bool _invalidState; - bool _needStateSync; + bool _invalidState = false; + bool _needStateSync = true; PipelineStageState() : _pipeline(), @@ -291,6 +306,16 @@ protected: {} } _pipeline; + // Output stage + void do_setFramebuffer(Batch& batch, uint32 paramOffset); + + struct OutputStageState { + + FramebufferPointer _framebuffer = nullptr; + + OutputStageState() {} + } _output; + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp index 12eab21686..3eeedc5dc3 100755 --- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp +++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp @@ -35,7 +35,7 @@ GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { if (!object) { object = new GLBuffer(); glGenBuffers(1, &object->_buffer); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); Backend::setGPUObject(buffer, object); } @@ -48,7 +48,7 @@ GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { object->_stamp = buffer.getSysmem().getStamp(); object->_size = buffer.getSysmem().getSize(); //} - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); return object; } diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index 982d2656a0..fcaa28aaaa 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -83,7 +83,7 @@ void GLBackend::updateInput() { glDisableVertexAttribArray(i); } } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _input._attributeActivation.flip(i); } @@ -108,7 +108,7 @@ void GLBackend::updateInput() { if (_input._buffersState.test(bufferNum) || _input._invalidFormat) { GLuint vbo = gpu::GLBackend::getBufferID((*buffers[bufferNum])); glBindBuffer(GL_ARRAY_BUFFER, vbo); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _input._buffersState[bufferNum] = false; for (unsigned int i = 0; i < channel._slots.size(); i++) { @@ -142,7 +142,7 @@ void GLBackend::updateInput() { glVertexAttribPointer(slot, count, type, isNormalized, stride, reinterpret_cast(pointer)); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } } } @@ -219,5 +219,5 @@ void GLBackend::do_setIndexBuffer(Batch& batch, uint32 paramOffset) { } else { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp new file mode 100755 index 0000000000..7b2deb64d2 --- /dev/null +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -0,0 +1,169 @@ +// +// GLBackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GPULogging.h" +#include "GLBackendShared.h" + + +GLBackend::GLFramebuffer::GLFramebuffer() {} + +GLBackend::GLFramebuffer::~GLFramebuffer() { + if (_fbo != 0) { + glDeleteFramebuffers(1, &_fbo); + } +} + +GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) { + GLFramebuffer* object = Backend::getGPUObject(framebuffer); + + // If GPU object already created and in sync + if (object) { + return object; + } else if (framebuffer.isEmpty()) { + // NO framebuffer definition yet so let's avoid thinking + return nullptr; + } + + // need to have a gpu object? + if (!object) { + GLuint fbo; + glGenFramebuffers(1, &fbo); + (void) CHECK_GL_ERROR(); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + unsigned int nbColorBuffers = 0; + GLenum colorBuffers[16]; + if (framebuffer.hasColor()) { + static const GLenum colorAttachments[] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + GL_COLOR_ATTACHMENT8, + GL_COLOR_ATTACHMENT9, + GL_COLOR_ATTACHMENT10, + GL_COLOR_ATTACHMENT11, + GL_COLOR_ATTACHMENT12, + GL_COLOR_ATTACHMENT13, + GL_COLOR_ATTACHMENT14, + GL_COLOR_ATTACHMENT15 }; + + int unit = 0; + for (auto& b : framebuffer.getRenderBuffers()) { + auto surface = b._texture; + if (surface) { + auto gltexture = GLBackend::syncGPUObject(*surface); + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + } + colorBuffers[nbColorBuffers] = colorAttachments[unit]; + nbColorBuffers++; + unit++; + } + } + } +#if (GPU_FEATURE_PROFILE == GPU_LEGACY) + // for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer... + else { + GLuint renderBuffer = 0; + glGenRenderbuffers(1, &renderBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight()); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer); + CHECK_GL_ERROR(); + } +#endif + + + if (framebuffer.hasDepthStencil()) { + auto surface = framebuffer.getDepthStencilBuffer(); + if (surface) { + auto gltexture = GLBackend::syncGPUObject(*surface); + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gltexture->_texture, 0); + } + } + } + + // Last but not least, define where we draw + if (nbColorBuffers > 0) { + glDrawBuffers(nbColorBuffers, colorBuffers); + } else { + glDrawBuffer( GL_NONE ); + } + + // Now check for completness + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + bool result = false; + switch (status) { + case GL_FRAMEBUFFER_COMPLETE : + // Success ! + result = true; + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : + qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : + qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER : + qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER : + qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; + break; + case GL_FRAMEBUFFER_UNSUPPORTED : + qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + break; + } + if (!result && fbo) { + glDeleteFramebuffers( 1, &fbo ); + return nullptr; + } + + + // All is green, assign the gpuobject to the Framebuffer + object = new GLFramebuffer(); + object->_fbo = fbo; + Backend::setGPUObject(framebuffer, object); + } + + return object; +} + + + +GLuint GLBackend::getFramebufferID(const FramebufferPointer& framebuffer) { + if (!framebuffer) { + return 0; + } + GLFramebuffer* object = GLBackend::syncGPUObject(*framebuffer); + if (object) { + return object->_fbo; + } else { + return 0; + } +} + +void GLBackend::do_setFramebuffer(Batch& batch, uint32 paramOffset) { + auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + + if (_output._framebuffer != framebuffer) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, getFramebufferID(framebuffer)); + _output._framebuffer = framebuffer; + } +} + diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index d285cdd8d8..48a45ce93f 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -103,7 +103,7 @@ void GLBackend::do_setPipeline(Batch& batch, uint32 paramOffset) { // THis should be done on Pipeline::update... if (_pipeline._invalidProgram) { glUseProgram(_pipeline._program); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._invalidProgram = false; } } @@ -122,7 +122,7 @@ void GLBackend::updatePipeline() { if (_pipeline._invalidProgram) { // doing it here is aproblem for calls to glUniform.... so will do it on assing... glUseProgram(_pipeline._program); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._invalidProgram = false; } @@ -164,7 +164,7 @@ void GLBackend::do_setUniformBuffer(Batch& batch, uint32 paramOffset) { // GLuint bo = getBufferID(*uniformBuffer); //glUniformBufferEXT(_shader._program, slot, bo); #endif - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void GLBackend::do_setUniformTexture(Batch& batch, uint32 paramOffset) { @@ -175,6 +175,6 @@ void GLBackend::do_setUniformTexture(Batch& batch, uint32 paramOffset) { glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(GL_TEXTURE_2D, to); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index 7707922b81..2d3eacd37f 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -237,26 +237,26 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) { } } -State::ComparisonFunction comparisonFuncFromGL(GLenum func) { +ComparisonFunction comparisonFuncFromGL(GLenum func) { if (func == GL_NEVER) { - return State::NEVER; + return NEVER; } else if (func == GL_LESS) { - return State::LESS; + return LESS; } else if (func == GL_EQUAL) { - return State::EQUAL; + return EQUAL; } else if (func == GL_LEQUAL) { - return State::LESS_EQUAL; + return LESS_EQUAL; } else if (func == GL_GREATER) { - return State::GREATER; + return GREATER; } else if (func == GL_NOTEQUAL) { - return State::NOT_EQUAL; + return NOT_EQUAL; } else if (func == GL_GEQUAL) { - return State::GREATER_EQUAL; + return GREATER_EQUAL; } else if (func == GL_ALWAYS) { - return State::ALWAYS; + return ALWAYS; } - return State::ALWAYS; + return ALWAYS; } State::StencilOp stencilOpFromGL(GLenum stencilOp) { @@ -458,7 +458,7 @@ void GLBackend::getCurrentGLState(State::Data& state) { | (mask[3] ? State::WRITE_ALPHA : 0); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } void GLBackend::syncPipelineStateCache() { @@ -485,7 +485,7 @@ void GLBackend::do_setStateFillMode(int32 mode) { if (_pipeline._stateCache.fillMode != mode) { static GLenum GL_FILL_MODES[] = { GL_POINT, GL_LINE, GL_FILL }; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.fillMode = State::FillMode(mode); } @@ -501,7 +501,7 @@ void GLBackend::do_setStateCullMode(int32 mode) { glEnable(GL_CULL_FACE); glCullFace(GL_CULL_MODES[mode]); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.cullMode = State::CullMode(mode); } @@ -511,7 +511,7 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { if (_pipeline._stateCache.frontFaceClockwise != isClockwise) { static GLenum GL_FRONT_FACES[] = { GL_CCW, GL_CW }; glFrontFace(GL_FRONT_FACES[isClockwise]); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.frontFaceClockwise = isClockwise; } @@ -524,7 +524,7 @@ void GLBackend::do_setStateDepthClipEnable(bool enable) { } else { glDisable(GL_DEPTH_CLAMP); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.depthClipEnable = enable; } @@ -537,7 +537,7 @@ void GLBackend::do_setStateScissorEnable(bool enable) { } else { glDisable(GL_SCISSOR_TEST); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.scissorEnable = enable; } @@ -550,7 +550,7 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { } else { glDisable(GL_MULTISAMPLE); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.multisampleEnable = enable; } @@ -565,7 +565,7 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { glDisable(GL_POINT_SMOOTH); glDisable(GL_LINE_SMOOTH); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.antialisedLineEnable = enable; } @@ -637,7 +637,7 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S } else { glDisable(GL_STENCIL_TEST); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.stencilActivation = activation; _pipeline._stateCache.stencilTestFront = frontTest; @@ -652,7 +652,7 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { } else { glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); _pipeline._stateCache.alphaToCoverageEnable = enable; } } @@ -684,7 +684,7 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { GL_MAX }; glBlendEquationSeparate(GL_BLEND_OPS[function.getOperationColor()], GL_BLEND_OPS[function.getOperationAlpha()]); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); static GLenum BLEND_ARGS[] = { GL_ZERO, @@ -706,7 +706,7 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { glBlendFuncSeparate(BLEND_ARGS[function.getSourceColor()], BLEND_ARGS[function.getDestinationColor()], BLEND_ARGS[function.getSourceAlpha()], BLEND_ARGS[function.getDestinationAlpha()]); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } else { glDisable(GL_BLEND); } @@ -735,5 +735,5 @@ void GLBackend::do_setStateBlendFactor(Batch& batch, uint32 paramOffset) { batch._params[paramOffset + 3]._float); glBlendColor(factor.x, factor.y, factor.z, factor.w); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index 2bbb3d633f..da39ab16fa 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -16,6 +16,7 @@ GLBackend::GLTexture::GLTexture() : _storageStamp(0), _contentStamp(0), _texture(0), + _target(GL_TEXTURE_2D), _size(0) {} @@ -144,7 +145,41 @@ public: texel.internalFormat = GL_RED; break; case gpu::DEPTH: + texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT; + switch (dstFormat.getType()) { + case gpu::UINT32: + case gpu::INT32: + case gpu::NUINT32: + case gpu::NINT32: { + texel.internalFormat = GL_DEPTH_COMPONENT32; + break; + } + case gpu::NFLOAT: + case gpu::FLOAT: { + texel.internalFormat = GL_DEPTH_COMPONENT32F; + break; + } + case gpu::UINT16: + case gpu::INT16: + case gpu::NUINT16: + case gpu::NINT16: + case gpu::HALF: + case gpu::NHALF: { + texel.internalFormat = GL_DEPTH_COMPONENT16; + break; + } + case gpu::UINT8: + case gpu::INT8: + case gpu::NUINT8: + case gpu::NINT8: { + texel.internalFormat = GL_DEPTH_COMPONENT24; + break; + } + case gpu::NUM_TYPES: { // quiet compiler + Q_UNREACHABLE(); + } + } break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; @@ -247,80 +282,91 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { if (!object) { object = new GLTexture(); glGenTextures(1, &object->_texture); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); Backend::setGPUObject(texture, object); } // GO through the process of allocating the correct storage and/or update the content switch (texture.getType()) { case Texture::TEX_2D: { - if (needUpdate) { - if (texture.isStoredMipAvailable(0)) { - GLint boundTex = -1; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - - Texture::PixelsPointer mip = texture.accessStoredMip(0); - const GLvoid* bytes = mip->_sysmem.read(); - Element srcFormat = mip->_format; - - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glBindTexture(GL_TEXTURE_2D, object->_texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, - texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, - texelFormat.format, texelFormat.type, bytes); - - if (texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - - // At this point the mip piels have been loaded, we can notify - texture.notifyGPULoaded(0); - - glBindTexture(GL_TEXTURE_2D, boundTex); - object->_contentStamp = texture.getDataStamp(); - } - } else { - const GLvoid* bytes = 0; - Element srcFormat = texture.getTexelFormat(); - if (texture.isStoredMipAvailable(0)) { - Texture::PixelsPointer mip = texture.accessStoredMip(0); - - bytes = mip->_sysmem.read(); - srcFormat = mip->_format; - - object->_contentStamp = texture.getDataStamp(); - } - + if (texture.getNumSlices() == 1) { GLint boundTex = -1; glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); glBindTexture(GL_TEXTURE_2D, object->_texture); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glTexImage2D(GL_TEXTURE_2D, 0, - texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, - texelFormat.format, texelFormat.type, bytes); + if (needUpdate) { + if (texture.isStoredMipAvailable(0)) { + Texture::PixelsPointer mip = texture.accessStoredMip(0); + const GLvoid* bytes = mip->_sysmem.read(); + Element srcFormat = mip->_format; + + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - if (bytes && texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glBindTexture(GL_TEXTURE_2D, object->_texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, + texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, + texelFormat.format, texelFormat.type, bytes); + + if (texture.isAutogenerateMips()) { + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + + object->_target = GL_TEXTURE_2D; + + syncSampler(texture.getSampler(), texture.getType(), object); + + + // At this point the mip piels have been loaded, we can notify + texture.notifyGPULoaded(0); + + object->_contentStamp = texture.getDataStamp(); + } + } else { + const GLvoid* bytes = 0; + Element srcFormat = texture.getTexelFormat(); + if (texture.isStoredMipAvailable(0)) { + Texture::PixelsPointer mip = texture.accessStoredMip(0); + + bytes = mip->_sysmem.read(); + srcFormat = mip->_format; + + object->_contentStamp = texture.getDataStamp(); + } + + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); + + glTexImage2D(GL_TEXTURE_2D, 0, + texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, + texelFormat.format, texelFormat.type, bytes); + + if (bytes && texture.isAutogenerateMips()) { + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + object->_target = GL_TEXTURE_2D; + + syncSampler(texture.getSampler(), texture.getType(), object); + + // At this point the mip piels have been loaded, we can notify + texture.notifyGPULoaded(0); + + object->_storageStamp = texture.getStamp(); + object->_size = texture.getSize(); } - // At this point the mip piels have been loaded, we can notify - texture.notifyGPULoaded(0); - glBindTexture(GL_TEXTURE_2D, boundTex); - object->_storageStamp = texture.getStamp(); - object->_size = texture.getSize(); } break; } default: qCDebug(gpulogging) << "GLBackend::syncGPUObject(const Texture&) case for Texture Type " << texture.getType() << " not supported"; } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); return object; } @@ -339,3 +385,70 @@ GLuint GLBackend::getTextureID(const TexturePointer& texture) { } } +void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, GLTexture* object) { + if (!object) return; + if (!object->_texture) return; + + class GLFilterMode { + public: + GLint minFilter; + GLint magFilter; + }; + static const GLFilterMode filterModes[] = { + {GL_NEAREST, GL_NEAREST}, //FILTER_MIN_MAG_POINT, + {GL_NEAREST, GL_LINEAR}, //FILTER_MIN_POINT_MAG_LINEAR, + {GL_LINEAR, GL_NEAREST}, //FILTER_MIN_LINEAR_MAG_POINT, + {GL_LINEAR, GL_LINEAR}, //FILTER_MIN_MAG_LINEAR, + + {GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, //FILTER_MIN_MAG_MIP_POINT, + {GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, //FILTER_MIN_MAG_MIP_POINT, + {GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, //FILTER_MIN_MAG_POINT_MIP_LINEAR, + {GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR}, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, + {GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR}, //FILTER_MIN_POINT_MAG_MIP_LINEAR, + {GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST}, //FILTER_MIN_LINEAR_MAG_MIP_POINT, + {GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST}, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, + {GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, //FILTER_MIN_MAG_LINEAR_MIP_POINT, + {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}, //FILTER_MIN_MAG_MIP_LINEAR, + {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} //FILTER_ANISOTROPIC, + }; + + auto fm = filterModes[sampler.getFilter()]; + glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); + glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); + + static const GLenum comparisonFuncs[] = { + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS }; + + if (sampler.doComparison()) { + glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(object->_target, GL_TEXTURE_COMPARE_FUNC, comparisonFuncs[sampler.getComparisonFunction()]); + } else { + glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + + static const GLenum wrapModes[] = { + GL_REPEAT, // WRAP_REPEAT, + GL_MIRRORED_REPEAT, // WRAP_MIRROR, + GL_CLAMP_TO_EDGE, // WRAP_CLAMP, + GL_CLAMP_TO_BORDER, // WRAP_BORDER, + GL_MIRROR_CLAMP_TO_EDGE_EXT }; // WRAP_MIRROR_ONCE, + + glTexParameteri(object->_target, GL_TEXTURE_WRAP_S, wrapModes[sampler.getWrapModeU()]); + glTexParameteri(object->_target, GL_TEXTURE_WRAP_T, wrapModes[sampler.getWrapModeV()]); + glTexParameteri(object->_target, GL_TEXTURE_WRAP_R, wrapModes[sampler.getWrapModeW()]); + + glTexParameterfv(object->_target, GL_TEXTURE_BORDER_COLOR, (const float*) &sampler.getBorderColor()); + glTexParameteri(object->_target, GL_TEXTURE_BASE_LEVEL, sampler.getMipOffset()); + glTexParameterf(object->_target, GL_TEXTURE_MIN_LOD, (float) sampler.getMinMip()); + glTexParameterf(object->_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + glTexParameterf(object->_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + (void) CHECK_GL_ERROR(); + +} diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index f0318a66aa..88c993985f 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -109,7 +109,7 @@ void GLBackend::updateTransform() { } glLoadMatrixf(reinterpret_cast< const GLfloat* >(&_transform._projection)); - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } if (_transform._invalidModel || _transform._invalidView) { @@ -141,7 +141,7 @@ void GLBackend::updateTransform() { // glLoadIdentity(); } } - CHECK_GL_ERROR(); + (void) CHECK_GL_ERROR(); } #endif diff --git a/libraries/gpu/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp index 708ca0f627..7d2757e15c 100644 --- a/libraries/gpu/src/gpu/Resource.cpp +++ b/libraries/gpu/src/gpu/Resource.cpp @@ -16,6 +16,8 @@ using namespace gpu; +const Element Element::COLOR_RGBA_32 = Element(VEC4, UINT8, RGBA); + Resource::Size Resource::Sysmem::allocateMemory(Byte** dataAllocated, Size size) { if ( !dataAllocated ) { qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer."; diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index 84f44d8efd..c9bd38efeb 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -41,20 +41,9 @@ public: State(); virtual ~State(); - const Stamp getStamp() const { return _stamp; } - - enum ComparisonFunction { - NEVER = 0, - LESS, - EQUAL, - LESS_EQUAL, - GREATER, - NOT_EQUAL, - GREATER_EQUAL, - ALWAYS, - - NUM_COMPARISON_FUNCS, - }; + Stamp getStamp() const { return _stamp; } + + typedef ::gpu::ComparisonFunction ComparisonFunction; enum FillMode { FILL_POINT = 0, @@ -415,5 +404,4 @@ typedef std::vector< StatePointer > States; }; - #endif diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index b1aaec665f..7cd859cbf5 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -101,9 +101,9 @@ public: uint32 getNumAttributes() const { return _attributes.size(); } const AttributeMap& getAttributes() const { return _attributes; } - uint8 getNumChannels() const { return _channels.size(); } + uint8 getNumChannels() const { return _channels.size(); } const ChannelMap& getChannels() const { return _channels; } - const Offset getChannelStride(Slot channel) const { return _channels.at(channel)._stride; } + Offset getChannelStride(Slot channel) const { return _channels.at(channel)._stride; } uint32 getElementTotalSize() const { return _elementTotalSize; } diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 43d8c991b6..f73b24411b 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -1,348 +1,355 @@ -// -// Texture.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 1/17/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "Texture.h" -#include -#include - -using namespace gpu; - -Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : - _sysmem(size, bytes), - _format(format), - _isGPULoaded(false) { -} - -Texture::Pixels::~Pixels() { -} - -void Texture::Storage::assignTexture(Texture* texture) { - _texture = texture; -} - -Stamp Texture::Storage::getStamp(uint16 level) const { - PixelsPointer mip = getMip(level); - if (mip) { - return mip->_sysmem.getStamp(); - } - return 0; -} - -void Texture::Storage::reset() { - _mips.clear(); -} - -Texture::PixelsPointer Texture::Storage::editMip(uint16 level) { - if (level < _mips.size()) { - return _mips[level]; - } - return PixelsPointer(); -} - -const Texture::PixelsPointer Texture::Storage::getMip(uint16 level) const { - if (level < _mips.size()) { - return _mips[level]; - } - return PixelsPointer(); -} - -void Texture::Storage::notifyGPULoaded(uint16 level) const { - PixelsPointer mip = getMip(level); - if (mip) { - mip->_isGPULoaded = true; - mip->_sysmem.resize(0); - } -} - -bool Texture::Storage::isMipAvailable(uint16 level) const { - PixelsPointer mip = getMip(level); - return (mip && mip->_sysmem.getSize()); -} - -bool Texture::Storage::allocateMip(uint16 level) { - bool changed = false; - if (level >= _mips.size()) { - _mips.resize(level+1, PixelsPointer()); - changed = true; - } - - if (!_mips[level]) { - _mips[level] = PixelsPointer(new Pixels()); - changed = true; - } - - return changed; -} - -bool Texture::Storage::assignMipData(uint16 level, const Element& format, Size size, const Byte* bytes) { - // Ok we should be able to do that... - allocateMip(level); - auto mip = _mips[level]; - mip->_format = format; - Size allocated = mip->_sysmem.setData(size, bytes); - mip->_isGPULoaded = false; - - return allocated == size; -} - -Texture* Texture::create1D(const Element& texelFormat, uint16 width) { - return create(TEX_1D, texelFormat, width, 1, 1, 1, 1); -} - -Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height) { - return create(TEX_2D, texelFormat, width, height, 1, 1, 1); -} - -Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth) { - return create(TEX_3D, texelFormat, width, height, depth, 1, 1); -} - -Texture* Texture::createCube(const Element& texelFormat, uint16 width) { - return create(TEX_CUBE, texelFormat, width, width, 1, 1, 1); -} - -Texture* Texture::create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) -{ - Texture* tex = new Texture(); - tex->_storage.reset(new Storage()); - tex->_storage->_texture = tex; - tex->_type = type; - tex->_maxMip = 0; - tex->resize(type, texelFormat, width, height, depth, numSamples, numSlices); - - return tex; -} - -Texture* Texture::createFromStorage(Storage* storage) { - Texture* tex = new Texture(); - tex->_storage.reset(storage); - storage->assignTexture(tex); - return tex; -} - -Texture::Texture(): - Resource(), - _storage(), - _stamp(0), - _size(0), - _width(1), - _height(1), - _depth(1), - _numSamples(1), - _numSlices(1), - _maxMip(0), - _type(TEX_1D), - _autoGenerateMips(false), - _defined(false) -{ -} - -Texture::~Texture() -{ -} - -Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) { - if (width && height && depth && numSamples && numSlices) { - bool changed = false; - - if ( _type != type) { - _type = type; - changed = true; - } - - if (_numSlices != numSlices) { - _numSlices = numSlices; - changed = true; - } - - numSamples = evalNumSamplesUsed(numSamples); - if ((_type >= TEX_2D) && (_numSamples != numSamples)) { - _numSamples = numSamples; - changed = true; - } - - if (_width != width) { - _width = width; - changed = true; - } - - if ((_type >= TEX_2D) && (_height != height)) { - _height = height; - changed = true; - } - - - if ((_type >= TEX_3D) && (_depth != depth)) { - _depth = depth; - changed = true; - } - - // Evaluate the new size with the new format - const int DIM_SIZE[] = {1, 1, 1, 6}; - uint32_t size = DIM_SIZE[_type] *_width * _height * _depth * _numSamples * texelFormat.getSize(); - - // If size change then we need to reset - if (changed || (size != getSize())) { - _size = size; - _storage->reset(); - _stamp++; - } - - // TexelFormat might have change, but it's mostly interpretation - if (texelFormat != _texelFormat) { - _texelFormat = texelFormat; - _stamp++; - } - - // Here the Texture has been fully defined from the gpu point of view (size and format) - _defined = true; - } else { - _stamp++; - } - - return _size; -} - -Texture::Size Texture::resize1D(uint16 width, uint16 numSamples) { - return resize(TEX_1D, getTexelFormat(), width, 1, 1, numSamples, 1); -} -Texture::Size Texture::resize2D(uint16 width, uint16 height, uint16 numSamples) { - return resize(TEX_2D, getTexelFormat(), width, height, 1, numSamples, 1); -} -Texture::Size Texture::resize3D(uint16 width, uint16 height, uint16 depth, uint16 numSamples) { - return resize(TEX_3D, getTexelFormat(), width, height, depth, numSamples, 1); -} -Texture::Size Texture::resizeCube(uint16 width, uint16 numSamples) { - return resize(TEX_CUBE, getTexelFormat(), width, 1, 1, numSamples, 1); -} - -Texture::Size Texture::reformat(const Element& texelFormat) { - return resize(_type, texelFormat, getWidth(), getHeight(), getDepth(), getNumSamples(), getNumSlices()); -} - -bool Texture::isColorRenderTarget() const { - return (_texelFormat.getSemantic() == gpu::RGBA); -} - -bool Texture::isDepthStencilRenderTarget() const { - return (_texelFormat.getSemantic() == gpu::DEPTH) || (_texelFormat.getSemantic() == gpu::DEPTH_STENCIL); -} - -uint16 Texture::evalDimNumMips(uint16 size) { - double largerDim = size; - double val = log(largerDim)/log(2.0); - return 1 + (uint16) val; -} - -// The number mips that the texture could have if all existed -// = log2(max(width, height, depth)) -uint16 Texture::evalNumMips() const { - double largerDim = std::max(std::max(_width, _height), _depth); - double val = log(largerDim)/log(2.0); - return 1 + (uint16) val; -} - -uint16 Texture::maxMip() const { - return _maxMip; -} - -bool Texture::assignStoredMip(uint16 level, const Element& format, Size size, const Byte* bytes) { - // Check that level accessed make sense - if (level != 0) { - if (_autoGenerateMips) { - return false; - } - if (level >= evalNumMips()) { - return false; - } - } - - // THen check that the mem buffer passed make sense with its format - Size expectedSize = evalStoredMipSize(level, format); - if (size == expectedSize) { - _storage->assignMipData(level, format, size, bytes); - _stamp++; - return true; - } else if (size > expectedSize) { - // NOTE: We are facing this case sometime because apparently QImage (from where we get the bits) is generating images - // and alligning the line of pixels to 32 bits. - // We should probably consider something a bit more smart to get the correct result but for now (UI elements) - // it seems to work... - _storage->assignMipData(level, format, size, bytes); - _stamp++; - return true; - } - - return false; -} - -uint16 Texture::autoGenerateMips(uint16 maxMip) { - _autoGenerateMips = true; - _maxMip = std::min((uint16) (evalNumMips() - 1), maxMip); - _stamp++; - return _maxMip; -} - -uint16 Texture::getStoredMipWidth(uint16 level) const { - PixelsPointer mip = accessStoredMip(level); - if (mip && mip->_sysmem.getSize()) { - return evalMipWidth(level); - } - return 0; -} - -uint16 Texture::getStoredMipHeight(uint16 level) const { - PixelsPointer mip = accessStoredMip(level); - if (mip && mip->_sysmem.getSize()) { - return evalMipHeight(level); - } - return 0; -} - -uint16 Texture::getStoredMipDepth(uint16 level) const { - PixelsPointer mip = accessStoredMip(level); - if (mip && mip->_sysmem.getSize()) { - return evalMipDepth(level); - } - return 0; -} - -uint32 Texture::getStoredMipNumTexels(uint16 level) const { - PixelsPointer mip = accessStoredMip(level); - if (mip && mip->_sysmem.getSize()) { - return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level); - } - return 0; -} - -uint32 Texture::getStoredMipSize(uint16 level) const { - PixelsPointer mip = accessStoredMip(level); - if (mip && mip->_sysmem.getSize()) { - return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level) * getTexelFormat().getSize(); - } - return 0; -} - -uint16 Texture::evalNumSamplesUsed(uint16 numSamplesTried) { - uint16 sample = numSamplesTried; - if (numSamplesTried <= 1) - sample = 1; - else if (numSamplesTried < 4) - sample = 2; - else if (numSamplesTried < 8) - sample = 4; - else if (numSamplesTried < 16) - sample = 8; - else - sample = 8; - - return sample; -} +// +// Texture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/17/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Texture.h" +#include +#include + +using namespace gpu; + +Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : + _sysmem(size, bytes), + _format(format), + _isGPULoaded(false) { +} + +Texture::Pixels::~Pixels() { +} + +void Texture::Storage::assignTexture(Texture* texture) { + _texture = texture; +} + +Stamp Texture::Storage::getStamp(uint16 level) const { + PixelsPointer mip = getMip(level); + if (mip) { + return mip->_sysmem.getStamp(); + } + return 0; +} + +void Texture::Storage::reset() { + _mips.clear(); +} + +Texture::PixelsPointer Texture::Storage::editMip(uint16 level) { + if (level < _mips.size()) { + return _mips[level]; + } + return PixelsPointer(); +} + +const Texture::PixelsPointer Texture::Storage::getMip(uint16 level) const { + if (level < _mips.size()) { + return _mips[level]; + } + return PixelsPointer(); +} + +void Texture::Storage::notifyGPULoaded(uint16 level) const { + PixelsPointer mip = getMip(level); + if (mip) { + mip->_isGPULoaded = true; + mip->_sysmem.resize(0); + } +} + +bool Texture::Storage::isMipAvailable(uint16 level) const { + PixelsPointer mip = getMip(level); + return (mip && mip->_sysmem.getSize()); +} + +bool Texture::Storage::allocateMip(uint16 level) { + bool changed = false; + if (level >= _mips.size()) { + _mips.resize(level+1, PixelsPointer()); + changed = true; + } + + if (!_mips[level]) { + _mips[level] = PixelsPointer(new Pixels()); + changed = true; + } + + return changed; +} + +bool Texture::Storage::assignMipData(uint16 level, const Element& format, Size size, const Byte* bytes) { + // Ok we should be able to do that... + allocateMip(level); + auto mip = _mips[level]; + mip->_format = format; + Size allocated = mip->_sysmem.setData(size, bytes); + mip->_isGPULoaded = false; + + return allocated == size; +} + +Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) { + return create(TEX_1D, texelFormat, width, 1, 1, 1, 1, sampler); +} + +Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { + return create(TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); +} + +Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler) { + return create(TEX_3D, texelFormat, width, height, depth, 1, 1, sampler); +} + +Texture* Texture::createCube(const Element& texelFormat, uint16 width, const Sampler& sampler) { + return create(TEX_CUBE, texelFormat, width, width, 1, 1, 1, sampler); +} + +Texture* Texture::create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler) +{ + Texture* tex = new Texture(); + tex->_storage.reset(new Storage()); + tex->_storage->_texture = tex; + tex->_type = type; + tex->_maxMip = 0; + tex->resize(type, texelFormat, width, height, depth, numSamples, numSlices); + + tex->_sampler = sampler; + + return tex; +} + +Texture* Texture::createFromStorage(Storage* storage) { + Texture* tex = new Texture(); + tex->_storage.reset(storage); + storage->assignTexture(tex); + return tex; +} + +Texture::Texture(): + Resource(), + _storage(), + _stamp(0), + _size(0), + _width(1), + _height(1), + _depth(1), + _numSamples(1), + _numSlices(1), + _maxMip(0), + _type(TEX_1D), + _autoGenerateMips(false), + _defined(false) +{ +} + +Texture::~Texture() +{ +} + +Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) { + if (width && height && depth && numSamples && numSlices) { + bool changed = false; + + if ( _type != type) { + _type = type; + changed = true; + } + + if (_numSlices != numSlices) { + _numSlices = numSlices; + changed = true; + } + + numSamples = evalNumSamplesUsed(numSamples); + if ((_type >= TEX_2D) && (_numSamples != numSamples)) { + _numSamples = numSamples; + changed = true; + } + + if (_width != width) { + _width = width; + changed = true; + } + + if ((_type >= TEX_2D) && (_height != height)) { + _height = height; + changed = true; + } + + + if ((_type >= TEX_3D) && (_depth != depth)) { + _depth = depth; + changed = true; + } + + // Evaluate the new size with the new format + const int DIM_SIZE[] = {1, 1, 1, 6}; + uint32_t size = DIM_SIZE[_type] *_width * _height * _depth * _numSamples * texelFormat.getSize(); + + // If size change then we need to reset + if (changed || (size != getSize())) { + _size = size; + _storage->reset(); + _stamp++; + } + + // TexelFormat might have change, but it's mostly interpretation + if (texelFormat != _texelFormat) { + _texelFormat = texelFormat; + _stamp++; + } + + // Here the Texture has been fully defined from the gpu point of view (size and format) + _defined = true; + } else { + _stamp++; + } + + return _size; +} + +Texture::Size Texture::resize1D(uint16 width, uint16 numSamples) { + return resize(TEX_1D, getTexelFormat(), width, 1, 1, numSamples, 1); +} +Texture::Size Texture::resize2D(uint16 width, uint16 height, uint16 numSamples) { + return resize(TEX_2D, getTexelFormat(), width, height, 1, numSamples, 1); +} +Texture::Size Texture::resize3D(uint16 width, uint16 height, uint16 depth, uint16 numSamples) { + return resize(TEX_3D, getTexelFormat(), width, height, depth, numSamples, 1); +} +Texture::Size Texture::resizeCube(uint16 width, uint16 numSamples) { + return resize(TEX_CUBE, getTexelFormat(), width, 1, 1, numSamples, 1); +} + +Texture::Size Texture::reformat(const Element& texelFormat) { + return resize(_type, texelFormat, getWidth(), getHeight(), getDepth(), getNumSamples(), getNumSlices()); +} + +bool Texture::isColorRenderTarget() const { + return (_texelFormat.getSemantic() == gpu::RGBA); +} + +bool Texture::isDepthStencilRenderTarget() const { + return (_texelFormat.getSemantic() == gpu::DEPTH) || (_texelFormat.getSemantic() == gpu::DEPTH_STENCIL); +} + +uint16 Texture::evalDimNumMips(uint16 size) { + double largerDim = size; + double val = log(largerDim)/log(2.0); + return 1 + (uint16) val; +} + +// The number mips that the texture could have if all existed +// = log2(max(width, height, depth)) +uint16 Texture::evalNumMips() const { + double largerDim = std::max(std::max(_width, _height), _depth); + double val = log(largerDim)/log(2.0); + return 1 + (uint16) val; +} + +uint16 Texture::maxMip() const { + return _maxMip; +} + +bool Texture::assignStoredMip(uint16 level, const Element& format, Size size, const Byte* bytes) { + // Check that level accessed make sense + if (level != 0) { + if (_autoGenerateMips) { + return false; + } + if (level >= evalNumMips()) { + return false; + } + } + + // THen check that the mem buffer passed make sense with its format + Size expectedSize = evalStoredMipSize(level, format); + if (size == expectedSize) { + _storage->assignMipData(level, format, size, bytes); + _stamp++; + return true; + } else if (size > expectedSize) { + // NOTE: We are facing this case sometime because apparently QImage (from where we get the bits) is generating images + // and alligning the line of pixels to 32 bits. + // We should probably consider something a bit more smart to get the correct result but for now (UI elements) + // it seems to work... + _storage->assignMipData(level, format, size, bytes); + _stamp++; + return true; + } + + return false; +} + +uint16 Texture::autoGenerateMips(uint16 maxMip) { + _autoGenerateMips = true; + _maxMip = std::min((uint16) (evalNumMips() - 1), maxMip); + _stamp++; + return _maxMip; +} + +uint16 Texture::getStoredMipWidth(uint16 level) const { + PixelsPointer mip = accessStoredMip(level); + if (mip && mip->_sysmem.getSize()) { + return evalMipWidth(level); + } + return 0; +} + +uint16 Texture::getStoredMipHeight(uint16 level) const { + PixelsPointer mip = accessStoredMip(level); + if (mip && mip->_sysmem.getSize()) { + return evalMipHeight(level); + } + return 0; +} + +uint16 Texture::getStoredMipDepth(uint16 level) const { + PixelsPointer mip = accessStoredMip(level); + if (mip && mip->_sysmem.getSize()) { + return evalMipDepth(level); + } + return 0; +} + +uint32 Texture::getStoredMipNumTexels(uint16 level) const { + PixelsPointer mip = accessStoredMip(level); + if (mip && mip->_sysmem.getSize()) { + return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level); + } + return 0; +} + +uint32 Texture::getStoredMipSize(uint16 level) const { + PixelsPointer mip = accessStoredMip(level); + if (mip && mip->_sysmem.getSize()) { + return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level) * getTexelFormat().getSize(); + } + return 0; +} + +uint16 Texture::evalNumSamplesUsed(uint16 numSamplesTried) { + uint16 sample = numSamplesTried; + if (numSamplesTried <= 1) + sample = 1; + else if (numSamplesTried < 4) + sample = 2; + else if (numSamplesTried < 8) + sample = 4; + else if (numSamplesTried < 16) + sample = 8; + else + sample = 8; + + return sample; +} + +void Texture::setSampler(const Sampler& sampler) { + _sampler = sampler; + _samplerStamp++; +} diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 3eed52c686..7bf936958b 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -16,6 +16,85 @@ namespace gpu { +class Sampler { +public: + + enum Filter { + FILTER_MIN_MAG_POINT, // top mip only + FILTER_MIN_POINT_MAG_LINEAR, // top mip only + FILTER_MIN_LINEAR_MAG_POINT, // top mip only + FILTER_MIN_MAG_LINEAR, // top mip only + + FILTER_MIN_MAG_MIP_POINT, + FILTER_MIN_MAG_POINT_MIP_LINEAR, + FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, + FILTER_MIN_POINT_MAG_MIP_LINEAR, + FILTER_MIN_LINEAR_MAG_MIP_POINT, + FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, + FILTER_MIN_MAG_LINEAR_MIP_POINT, + FILTER_MIN_MAG_MIP_LINEAR, + FILTER_ANISOTROPIC, + + NUM_FILTERS, + }; + + enum WrapMode { + WRAP_REPEAT = 0, + WRAP_MIRROR, + WRAP_CLAMP, + WRAP_BORDER, + WRAP_MIRROR_ONCE, + + NUM_WRAP_MODES + }; + + static const uint8 MAX_MIP_LEVEL = 0xFF; + + class Desc { + public: + glm::vec4 _borderColor{ 1.0f }; + uint32 _maxAnisotropy = 16; + + uint8 _wrapModeU = WRAP_REPEAT; + uint8 _wrapModeV = WRAP_REPEAT; + uint8 _wrapModeW = WRAP_REPEAT; + + uint8 _filter = FILTER_MIN_MAG_POINT; + uint8 _comparisonFunc = ALWAYS; + + uint8 _mipOffset = 0; + uint8 _minMip = 0; + uint8 _maxMip = MAX_MIP_LEVEL; + + Desc() {} + Desc(const Filter filter) : _filter(filter) {} + }; + + Sampler() {} + Sampler(const Filter filter) : _desc(filter) {} + Sampler(const Desc& desc) : _desc(desc) {} + ~Sampler() {} + + const glm::vec4& getBorderColor() const { return _desc._borderColor; } + + uint32 getMaxAnisotropy() const { return _desc._maxAnisotropy; } + + WrapMode getWrapModeU() const { return WrapMode(_desc._wrapModeU); } + WrapMode getWrapModeV() const { return WrapMode(_desc._wrapModeV); } + WrapMode getWrapModeW() const { return WrapMode(_desc._wrapModeW); } + + Filter getFilter() const { return Filter(_desc._filter); } + ComparisonFunction getComparisonFunction() const { return ComparisonFunction(_desc._comparisonFunc); } + bool doComparison() const { return getComparisonFunction() != ALWAYS; } + + uint8 getMipOffset() const { return _desc._mipOffset; } + uint8 getMinMip() const { return _desc._minMip; } + uint8 getMaxMip() const { return _desc._maxMip; } + +protected: + Desc _desc; +}; + class Texture : public Resource { public: @@ -61,10 +140,10 @@ public: TEX_CUBE, }; - static Texture* create1D(const Element& texelFormat, uint16 width); - static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height); - static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth); - static Texture* createCube(const Element& texelFormat, uint16 width); + static Texture* create1D(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler()); + static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler()); + static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler = Sampler()); + static Texture* createCube(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler()); static Texture* createFromStorage(Storage* storage); @@ -72,8 +151,8 @@ public: Texture& operator=(const Texture& buf); // deep copy of the sysmem texture ~Texture(); - const Stamp getStamp() const { return _stamp; } - const Stamp getDataStamp(uint16 level = 0) const { return _storage->getStamp(level); } + Stamp getStamp() const { return _stamp; } + Stamp getDataStamp(uint16 level = 0) const { return _storage->getStamp(level); } // The size in bytes of data stored in the texture Size getSize() const { return _size; } @@ -181,11 +260,21 @@ public: bool isDefined() const { return _defined; } + + // Own sampler + void setSampler(const Sampler& sampler); + const Sampler& getSampler() const { return _sampler; } + Stamp getSamplerStamp() const { return _samplerStamp; } + protected: std::unique_ptr< Storage > _storage; Stamp _stamp; + Sampler _sampler; + Stamp _samplerStamp; + + uint32 _size; Element _texelFormat; @@ -202,7 +291,7 @@ protected: bool _autoGenerateMips; bool _defined; - static Texture* create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices); + static Texture* create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler); Texture(); Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices); @@ -240,15 +329,25 @@ public: _subresource(0), _element(element) {}; - TextureView(const TexturePointer& texture, const Element& element) : + TextureView(const TexturePointer& texture, uint16 subresource, const Element& element) : _texture(texture), - _subresource(0), + _subresource(subresource), _element(element) {}; + + TextureView(const TexturePointer& texture, uint16 subresource) : + _texture(texture), + _subresource(subresource) + {}; + ~TextureView() {} TextureView(const TextureView& view) = default; TextureView& operator=(const TextureView& view) = default; + + explicit operator bool() const { return (_texture); } + bool operator !() const { return (!_texture); } }; +typedef std::vector TextureViews; }; diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index d5d3ba6c07..156f593421 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -97,7 +97,7 @@ const Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const auto vertices = &_vertexBuffer.get((*part)._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices - if ((*index) != PRIMITIVE_RESTART_INDEX) { + if ((*index) != (uint) PRIMITIVE_RESTART_INDEX) { partBound += vertices[(*index)]; } } diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp index ea77412140..e8c01c68aa 100755 --- a/libraries/model/src/model/Light.cpp +++ b/libraries/model/src/model/Light.cpp @@ -64,6 +64,10 @@ void Light::setIntensity(float intensity) { editSchema()._intensity = intensity; } +void Light::setAmbientIntensity(float intensity) { + editSchema()._ambientIntensity = intensity; +} + void Light::setMaximumRadius(float radius) { if (radius <= 0.f) { radius = 1.0f; diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index 8f6c663668..30e3a8edad 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -244,6 +244,10 @@ public: void setShowContour(float show); float getShowContour() const { return getSchema()._control.w; } + // 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); + float getAmbientIntensity() const { return getSchema()._ambientIntensity; } + // Spherical Harmonics storing the Ambien lighting approximation used for the Sun typed light void setAmbientSphere(const SphericalHarmonics& sphere) { _ambientSphere = sphere; } const SphericalHarmonics& getAmbientSphere() const { return _ambientSphere; } @@ -254,14 +258,14 @@ public: public: Vec4 _position{0.0f, 0.0f, 0.0f, 1.0f}; Vec3 _direction{0.0f, 0.0f, -1.0f}; - float _spare0{0.0f}; + float _ambientIntensity{0.0f}; Color _color{1.0f}; float _intensity{1.0f}; Vec4 _attenuation{1.0f}; Vec4 _spot{0.0f, 0.0f, 0.0f, 3.0f}; Vec4 _shadow{0.0f}; - Vec4 _control{0.0f}; + Vec4 _control{0.0f, 0.0f, 0.0f, 0.0f}; Schema() {} }; diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 41c6e075cf..1aaf0e8327 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -27,6 +27,7 @@ vec3 getLightDirection(Light l) { return l._direction.xyz; } // direction is -Z vec3 getLightColor(Light l) { return l._color.rgb; } float getLightIntensity(Light l) { return l._color.w; } +float getLightAmbientIntensity(Light l) { return l._direction.w; } float evalLightAttenuation(Light l, float r) { float d = max(r - l._attenuation.x, 0.0); diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index b72aa16497..77bc3f03f0 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -203,6 +203,7 @@ SunSkyStage::SunSkyStage() : _sunLight->setType(Light::SUN); setSunIntensity(1.0f); + setSunAmbientIntensity(0.5f); setSunColor(Vec3(1.0f, 1.0f, 1.0f)); // Default origin location is a special place in the world... @@ -249,12 +250,26 @@ void SunSkyStage::setOriginLocation(float longitude, float latitude, float altit invalidate(); } +void SunSkyStage::setSunModelEnable(bool isEnabled) { + _sunModelEnable = isEnabled; + invalidate(); +} + void SunSkyStage::setSunColor(const Vec3& color) { _sunLight->setColor(color); } void SunSkyStage::setSunIntensity(float intensity) { _sunLight->setIntensity(intensity); } +void SunSkyStage::setSunAmbientIntensity(float intensity) { + _sunLight->setAmbientIntensity(intensity); +} + +void SunSkyStage::setSunDirection(const Vec3& direction) { + if (!isSunModelEnabled()) { + _sunLight->setDirection(direction); + } +} // THe sun declinaison calculus is taken from https://en.wikipedia.org/wiki/Position_of_the_Sun double evalSunDeclinaison(double dayNumber) { @@ -271,19 +286,19 @@ void SunSkyStage::updateGraphicsObject() const { // And update the sunLAtitude as the declinaison depending of the time of the year _earthSunModel.setSunLatitude(evalSunDeclinaison(_yearTime)); - Vec3d sunLightDir = -_earthSunModel.getSurfaceSunDir(); - _sunLight->setDirection(Vec3(sunLightDir.x, sunLightDir.y, sunLightDir.z)); + if (isSunModelEnabled()) { + Vec3d sunLightDir = -_earthSunModel.getSurfaceSunDir(); + _sunLight->setDirection(Vec3(sunLightDir.x, sunLightDir.y, sunLightDir.z)); - double originAlt = _earthSunModel.getAltitude(); - _sunLight->setPosition(Vec3(0.0f, originAlt, 0.0f)); + double originAlt = _earthSunModel.getAltitude(); + _sunLight->setPosition(Vec3(0.0f, originAlt, 0.0f)); + } static int firstTime = 0; if (firstTime == 0) { firstTime++; gpu::Shader::makeProgram(*(_skyPipeline->getProgram())); - } - } void SunSkyStage::setSkybox(const SkyboxPointer& skybox) { diff --git a/libraries/model/src/model/Stage.h b/libraries/model/src/model/Stage.h index 30c96259ca..237265ed12 100644 --- a/libraries/model/src/model/Stage.h +++ b/libraries/model/src/model/Stage.h @@ -203,12 +203,22 @@ public: float getOriginLongitude() const { return _earthSunModel.getLongitude(); } float getOriginSurfaceAltitude() const { return _earthSunModel.getAltitude(); } + // Enable / disable the effect of the time and location on the sun direction and color + void setSunModelEnable(bool isEnabled); + bool isSunModelEnabled() const { return _sunModelEnable; } + // Sun properties void setSunColor(const Vec3& color); const Vec3& getSunColor() const { return getSunLight()->getColor(); } void setSunIntensity(float intensity); float getSunIntensity() const { return getSunLight()->getIntensity(); } + void setSunAmbientIntensity(float intensity); + float getSunAmbientIntensity() const { return getSunLight()->getAmbientIntensity(); } + // The sun direction is expressed in the world space + void setSunDirection(const Vec3& direction); + const Vec3& getSunDirection() const { return getSunLight()->getDirection(); } + LightPointer getSunLight() const { valid(); return _sunLight; } AtmospherePointer getAtmosphere() const { valid(); return _atmosphere; } @@ -223,10 +233,10 @@ protected: gpu::PipelinePointer _skyPipeline; - float _dayTime; - int _yearTime; - + float _dayTime = 12.0f; + int _yearTime = 0; mutable EarthSunModel _earthSunModel; + bool _sunModelEnable = true; mutable bool _invalid = true; void invalidate() const { _invalid = true; } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 1f1ada5592..2830a13ca7 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -366,7 +366,7 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) OAuthAccessToken newOAuthToken; newOAuthToken.token = accessToken; - qCDebug(networking) << "Setting new account manager access token to" << accessToken; + qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2); _accountInfo.setAccessToken(newOAuthToken); } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 73aaa63844..9b99c236b3 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -35,7 +35,6 @@ AddressManager::AddressManager() : _positionGetter(NULL), _orientationGetter(NULL) { - connect(qApp, &QCoreApplication::aboutToQuit, this, &AddressManager::storeCurrentAddress); } bool AddressManager::isConnected() { diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 0df4887b74..44e8dbef90 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -17,7 +17,9 @@ #include "NetworkLogging.h" #include "DataServerAccountInfo.h" +#ifndef __GNUC__ #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif DataServerAccountInfo::DataServerAccountInfo() : _accessToken(), @@ -33,7 +35,7 @@ DataServerAccountInfo::DataServerAccountInfo() : } -DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) { +DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; _xmppPassword = otherInfo._xmppPassword; diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index c0fb0ecb69..8a967d7818 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -33,6 +33,7 @@ HifiSockAddr::HifiSockAddr(const QHostAddress& address, quint16 port) : } HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) : + QObject(), _address(otherSockAddr._address), _port(otherSockAddr._port) { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 80ceccc407..a7057b4ed8 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -237,8 +237,6 @@ protected: HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; HifiSockAddr _stunSockAddr; - - QTimer* _silentNodeTimer; // XXX can BandwidthRecorder be used for this? int _numCollectedPackets; diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index c6026b3a23..de1b8f66ba 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -39,8 +39,7 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co } -NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) { - +NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) : QObject() { _uuid = otherPeer._uuid; _publicSocket = otherPeer._publicSocket; _localSocket = otherPeer._localSocket; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 76f66420bf..1e24b3f1a9 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -58,6 +58,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // handle ICE signal from DS so connection is attempted immediately connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer); + + // clear out NodeList when login is finished + connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset); // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); diff --git a/libraries/networking/src/OAuthAccessToken.cpp b/libraries/networking/src/OAuthAccessToken.cpp index b8ec58099f..0c14e5e074 100644 --- a/libraries/networking/src/OAuthAccessToken.cpp +++ b/libraries/networking/src/OAuthAccessToken.cpp @@ -31,7 +31,7 @@ OAuthAccessToken::OAuthAccessToken(const QJsonObject& jsonObject) : } -OAuthAccessToken::OAuthAccessToken(const OAuthAccessToken& otherToken) { +OAuthAccessToken::OAuthAccessToken(const OAuthAccessToken& otherToken) : QObject() { token = otherToken.token; refreshToken = otherToken.refreshToken; expiryTimestamp = otherToken.expiryTimestamp; diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 52153f9e83..a9ccec34bb 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_HAVE_ACCELERATION; + return VERSION_ENTITIES_HAVE_NAMES; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 9fb14854aa..7b12b1a089 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -119,6 +119,7 @@ PacketType packetTypeForPacket(const char* packet); int arithmeticCodingValueFromBuffer(const char* checkValue); int numBytesArithmeticCodingFromBuffer(const char* checkValue); +const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1; const PacketVersion VERSION_ENTITIES_HAVE_ANIMATION = 1; const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2; const PacketVersion VERSION_ENTITIES_SUPPORT_SPLIT_MTU = 3; @@ -135,6 +136,9 @@ const PacketVersion VERSION_ENTITIES_HAS_COLLISION_MODEL = 12; const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED = 13; const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID = 14; const PacketVersion VERSION_ENTITIES_HAVE_ACCELERATION = 15; -const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1; +const PacketVersion VERSION_ENTITIES_HAVE_UUIDS = 16; +const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_EXIST = 17; +const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_DYNAMIC_SHAPE = 18; +const PacketVersion VERSION_ENTITIES_HAVE_NAMES = 19; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index f142ce1831..2368b5e6d2 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -18,7 +18,9 @@ #include "NetworkLogging.h" #include "RSAKeypairGenerator.h" +#ifndef __GNUC__ #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif RSAKeypairGenerator::RSAKeypairGenerator(QObject* parent) : QObject(parent) diff --git a/libraries/networking/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h index d5fc006882..bcc9f9a1f5 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -47,6 +47,8 @@ public: /// How many received packets waiting are to be processed int packetsToProcessCount() const { return _packets.size(); } + virtual void terminating(); + public slots: void nodeKilled(SharedNodePointer node); @@ -71,8 +73,6 @@ protected: /// Override to do work after the packets processing loop. Default does nothing. virtual void postProcess() { } - virtual void terminating(); - protected: QVector _packets; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 79b4e7f437..43bcce4530 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -30,6 +30,17 @@ void ThreadedAssignment::setFinished(bool isFinished) { _isFinished = isFinished; if (_isFinished) { + if (_domainServerTimer) { + _domainServerTimer->stop(); + delete _domainServerTimer; + _domainServerTimer = nullptr; + } + if (_statsTimer) { + _statsTimer->stop(); + delete _statsTimer; + _statsTimer = nullptr; + } + aboutToFinish(); auto nodeList = DependencyManager::get(); @@ -63,15 +74,15 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy // this is a temp fix for Qt 5.3 - rebinding the node socket gives us readyRead for the socket on this thread nodeList->rebindNodeSocket(); - QTimer* domainServerTimer = new QTimer(this); - connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + _domainServerTimer = new QTimer(); + connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); + _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); if (shouldSendStats) { // send a stats packet every 1 second - QTimer* statsTimer = new QTimer(this); - connect(statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); - statsTimer->start(1000); + _statsTimer = new QTimer(); + connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); + _statsTimer->start(1000); } } diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 454baa85f2..590c2f56ca 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -28,8 +28,15 @@ public: public slots: /// threaded run of assignment virtual void run() = 0; + Q_INVOKABLE virtual void stop() { setFinished(true); } virtual void readPendingDatagrams() = 0; virtual void sendStatsPacket(); + +public slots: + virtual void aboutToQuit() { + QMetaObject::invokeMethod(this, "stop"); + } + signals: void finished(); @@ -38,6 +45,8 @@ protected: void commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats = true); bool _isFinished; QThread* _datagramProcessingThread; + QTimer* _domainServerTimer = nullptr; + QTimer* _statsTimer = nullptr; private slots: void checkInWithDomainServerOrExit(); diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index b1e92e2140..d53d29e444 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -380,9 +380,9 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch } // if this is the root, and there is more data to read, allow it to read it's element data... - if (destinationElement == _rootElement && rootElementHasData() && (bytesLeftToRead - bytesRead) > 0) { + if (destinationElement == _rootElement && rootElementHasData() && bytesLeftToRead > 0) { // tell the element to read the subsequent data - int rootDataSize = _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead - bytesRead, args); + int rootDataSize = _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); bytesRead += rootDataSize; bytesLeftToRead -= rootDataSize; } @@ -2087,7 +2087,7 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElement* element) { QFile persistFile(fileName); QVariantMap entityDescription; - qCDebug(octree, "Saving to file %s...", fileName); + qCDebug(octree, "Saving JSON SVO to file %s...", fileName); OctreeElement* top; if (element) { @@ -2096,7 +2096,7 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElement* element) { top = _rootElement; } - bool entityDescriptionSuccess = writeToMap(entityDescription, top); + bool entityDescriptionSuccess = writeToMap(entityDescription, top, true); if (entityDescriptionSuccess && persistFile.open(QIODevice::WriteOnly)) { persistFile.write(QJsonDocument::fromVariant(entityDescription).toJson()); } else { @@ -2108,7 +2108,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { std::ofstream file(fileName, std::ios::out|std::ios::binary); if(file.is_open()) { - qCDebug(octree, "Saving to file %s...", fileName); + qCDebug(octree, "Saving binary SVO to file %s...", fileName); PacketType expectedType = expectedDataPacketType(); PacketVersion expectedVersion = versionForPacketType(expectedType); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 21c3efc01d..d7fc58699f 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -331,7 +331,7 @@ public: void writeToFile(const char* filename, OctreeElement* element = NULL, QString persistAsFileType = "svo"); void writeToJSONFile(const char* filename, OctreeElement* element = NULL); void writeToSVOFile(const char* filename, OctreeElement* element = NULL); - virtual bool writeToMap(QVariantMap& entityDescription, OctreeElement* element) = 0; + virtual bool writeToMap(QVariantMap& entityDescription, OctreeElement* element, bool skipDefaultValues) = 0; // Octree importers bool readFromFile(const char* filename); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 674faa11c3..64947010a0 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -411,6 +411,20 @@ bool OctreePacketData::appendValue(const QString& string) { return success; } +bool OctreePacketData::appendValue(const QUuid& uuid) { + QByteArray bytes = uuid.toRfc4122(); + if (uuid.isNull()) { + return appendValue((uint16_t)0); // zero length for null uuid + } else { + uint16_t length = bytes.size(); + bool success = appendValue(length); + if (success) { + success = appendRawData((const unsigned char*)bytes.constData(), bytes.size()); + } + return success; + } +} + bool OctreePacketData::appendValue(const QByteArray& bytes) { bool success = appendRawData((const unsigned char*)bytes.constData(), bytes.size()); return success; diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 1c1576f509..992eca99ae 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -166,6 +166,9 @@ public: /// appends a string value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const QString& string); + /// appends a uuid value to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const QUuid& uuid); + /// appends a QByteArray value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const QByteArray& bytes); diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 9c7a43b12d..52dd2aa4ca 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -228,6 +228,7 @@ void OctreePersistThread::aboutToFinish() { qCDebug(octree) << "Persist thread about to finish..."; persist(); qCDebug(octree) << "Persist thread done with about to finish..."; + _stopThread = true; } void OctreePersistThread::persist() { @@ -285,6 +286,7 @@ void OctreePersistThread::restoreFromMostRecentBackup() { qCDebug(octree) << "DONE restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; } else { qCDebug(octree) << "ERROR while restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; + perror("ERROR while restoring backup file"); } } else { qCDebug(octree) << "NO BEST backup file found."; @@ -366,6 +368,7 @@ void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { qCDebug(octree) << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; } else { qCDebug(octree) << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; + perror("ERROR in rolling backup file"); } } } @@ -425,6 +428,7 @@ void OctreePersistThread::backup() { rule.lastBackup = now; // only record successful backup in this case. } else { qCDebug(octree) << "ERROR in backing up persist file..."; + perror("ERROR in backing up persist file"); } } else { qCDebug(octree) << "persist file " << _filename << " does not exist. " << diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index ae40cbfa15..4347934d87 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -164,9 +164,11 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) { return false; } -void OctreeRenderer::render(RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) { +void OctreeRenderer::render(RenderArgs::RenderMode renderMode, + RenderArgs::RenderSide renderSide, + RenderArgs::DebugFlags renderDebugFlags) { RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, renderSide, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + renderDebugFlags, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (_tree) { _tree->lockForRead(); _tree->recurseTreeWithOperation(renderOperation, &args); diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 4ee0865243..ca0914723f 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -52,7 +52,8 @@ public: /// render the content of the octree virtual void render(RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE, - RenderArgs::RenderSide renderSide = RenderArgs::MONO); + RenderArgs::RenderSide renderSide = RenderArgs::MONO, + RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE); ViewFrustum* getViewFrustum() const { return _viewFrustum; } void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; } diff --git a/libraries/physics/src/DynamicCharacterController.cpp b/libraries/physics/src/DynamicCharacterController.cpp index db84bea540..eebd8f0c88 100644 --- a/libraries/physics/src/DynamicCharacterController.cpp +++ b/libraries/physics/src/DynamicCharacterController.cpp @@ -8,11 +8,9 @@ const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); const float DEFAULT_GRAVITY = -5.0f; -const float TERMINAL_VELOCITY = 55.0f; const float JUMP_SPEED = 3.5f; const float MAX_FALL_HEIGHT = 20.0f; -const float MIN_HOVER_HEIGHT = 3.0f; const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0; const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; @@ -43,8 +41,8 @@ protected: DynamicCharacterController::DynamicCharacterController(AvatarData* avatarData) { _halfHeight = 1.0f; - _shape = NULL; - _rigidBody = NULL; + _shape = nullptr; + _rigidBody = nullptr; assert(avatarData); _avatarData = avatarData; @@ -96,7 +94,7 @@ void DynamicCharacterController::preStep(btCollisionWorld* collisionWorld) { } } -void DynamicCharacterController::playerStep(btCollisionWorld* dynaWorld,btScalar dt) { +void DynamicCharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) { btVector3 actualVelocity = _rigidBody->getLinearVelocity(); btScalar actualSpeed = actualVelocity.length(); @@ -245,7 +243,6 @@ void DynamicCharacterController::setEnabled(bool enabled) { // Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit. // Setting the ADD bit here works for all cases so we don't even bother checking other bits. _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; - setHovering(true); } else { if (_dynamicsWorld) { _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; @@ -253,6 +250,7 @@ void DynamicCharacterController::setEnabled(bool enabled) { _pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION; _isOnGround = false; } + setHovering(true); _enabled = enabled; } } @@ -264,7 +262,7 @@ void DynamicCharacterController::setDynamicsWorld(btDynamicsWorld* world) { _dynamicsWorld->removeRigidBody(_rigidBody); _dynamicsWorld->removeAction(this); } - _dynamicsWorld = NULL; + _dynamicsWorld = nullptr; } if (world && _rigidBody) { _dynamicsWorld = world; @@ -294,9 +292,9 @@ void DynamicCharacterController::updateShapeIfNecessary() { _pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE; // delete shape and RigidBody delete _rigidBody; - _rigidBody = NULL; + _rigidBody = nullptr; delete _shape; - _shape = NULL; + _shape = nullptr; // compute new dimensions from avatar's bounding box float x = _boxScale.x; @@ -316,11 +314,11 @@ void DynamicCharacterController::updateShapeIfNecessary() { // create new body float mass = 1.0f; btVector3 inertia(1.0f, 1.0f, 1.0f); - _rigidBody = new btRigidBody(mass, NULL, _shape, inertia); - _rigidBody->setSleepingThresholds (0.0f, 0.0f); - _rigidBody->setAngularFactor (0.0f); + _rigidBody = new btRigidBody(mass, nullptr, _shape, inertia); + _rigidBody->setSleepingThresholds(0.0f, 0.0f); + _rigidBody->setAngularFactor(0.0f); _rigidBody->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()), - glmToBullet(_avatarData->getPosition()))); + glmToBullet(_avatarData->getPosition()))); if (_isHovering) { _rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f)); } else { @@ -386,7 +384,7 @@ void DynamicCharacterController::preSimulation(btScalar timeStep) { setHovering(true); } - _walkVelocity = glmToBullet(_avatarData->getVelocity()); + _walkVelocity = glmToBullet(_avatarData->getTargetVelocity()); if (_pendingFlags & PENDING_FLAG_JUMP) { _pendingFlags &= ~ PENDING_FLAG_JUMP; @@ -408,6 +406,7 @@ void DynamicCharacterController::postSimulation() { _avatarData->setOrientation(rotation); _avatarData->setPosition(position - rotation * _shapeLocalOffset); + _avatarData->setVelocity(bulletToGLM(_rigidBody->getLinearVelocity())); } } diff --git a/libraries/physics/src/DynamicCharacterController.h b/libraries/physics/src/DynamicCharacterController.h index f98343c49b..06ff555f66 100644 --- a/libraries/physics/src/DynamicCharacterController.h +++ b/libraries/physics/src/DynamicCharacterController.h @@ -30,7 +30,7 @@ protected: glm::vec3 _shapeLocalOffset; glm::vec3 _boxScale; // used to compute capsule shape - AvatarData* _avatarData = NULL; + AvatarData* _avatarData = nullptr; bool _enabled; bool _isOnGround; @@ -41,7 +41,7 @@ protected: quint64 _jumpToHoverStart; uint32_t _pendingFlags; - btDynamicsWorld* _dynamicsWorld = NULL; + btDynamicsWorld* _dynamicsWorld = nullptr; btScalar _jumpSpeed; @@ -78,7 +78,8 @@ public: bool needsRemoval() const; bool needsAddition() const; void setEnabled(bool enabled); - bool isEnabled() const { return _enabled; } + bool isEnabled() const { return _enabled && _dynamicsWorld; } + void setDynamicsWorld(btDynamicsWorld* world); void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index d84daa6b42..a7245c32d4 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -18,6 +18,8 @@ #include "PhysicsHelpers.h" #include "PhysicsLogging.h" +static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f; +static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; QSet* _outgoingEntityList; @@ -33,8 +35,12 @@ void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) { _outgoingEntityList->insert(entity); } -EntityMotionState::EntityMotionState(EntityItem* entity) - : _entity(entity) { +EntityMotionState::EntityMotionState(EntityItem* entity) : + _entity(entity), + _accelerationNearlyGravityCount(0), + _shouldClaimSimulationOwnership(false), + _movingStepsWithoutSimulationOwner(0) +{ _type = MOTION_STATE_TYPE_ENTITY; assert(entity != NULL); } @@ -45,7 +51,7 @@ EntityMotionState::~EntityMotionState() { _entity = NULL; } -MotionType EntityMotionState::computeMotionType() const { +MotionType EntityMotionState::computeObjectMotionType() const { if (_entity->getCollisionsWillMove()) { return MOTION_TYPE_DYNAMIC; } @@ -62,6 +68,9 @@ void EntityMotionState::stepKinematicSimulation(quint64 now) { // which is different from physical kinematic motion (inside getWorldTransform()) // which steps in physics simulation time. _entity->simulate(now); + // TODO: we can't use measureBodyAcceleration() here because the entity + // has no RigidBody and the timestep is a little bit out of sync with the physics simulation anyway. + // Hence we must manually measure kinematic velocity and acceleration. } bool EntityMotionState::isMoving() const { @@ -71,7 +80,7 @@ bool EntityMotionState::isMoving() const { // This callback is invoked by the physics simulation in two cases: // (1) when the RigidBody is first added to the world // (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC) -// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's -- +// (2) at the beginning of each simulation step for KINEMATIC RigidBody's -- // it is an opportunity for outside code to update the object's simulation position void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { if (_isKinematic) { @@ -89,9 +98,10 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { worldTrans.setRotation(glmToBullet(_entity->getRotation())); } -// This callback is invoked by the physics simulation at the end of each simulation frame... +// This callback is invoked by the physics simulation at the end of each simulation step... // iff the corresponding RigidBody is DYNAMIC and has moved. void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { + measureBodyAcceleration(); _entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset()); _entity->setRotation(bulletToGLM(worldTrans.getRotation())); @@ -99,11 +109,23 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { getVelocity(v); _entity->setVelocity(v); - getAngularVelocity(v); - _entity->setAngularVelocity(v); + glm::vec3 av; + getAngularVelocity(av); + _entity->setAngularVelocity(av); _entity->setLastSimulated(usecTimestampNow()); + if (_entity->getSimulatorID().isNull() && isMoving()) { + // object is moving and has no owner. attempt to claim simulation ownership. + _movingStepsWithoutSimulationOwner++; + } else { + _movingStepsWithoutSimulationOwner = 0; + } + + if (_movingStepsWithoutSimulationOwner > 4) { // XXX maybe meters from our characterController ? + setShouldClaimSimulationOwnership(true); + } + _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; EntityMotionState::enqueueOutgoingEntity(_entity); @@ -116,76 +138,124 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { #endif } -void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) { - if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { +void EntityMotionState::updateBodyEasy(uint32_t flags, uint32_t step) { + if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_PHYSICS_NO_WAKE)) { if (flags & EntityItem::DIRTY_POSITION) { - _sentPosition = _entity->getPosition() - ObjectMotionState::getWorldOffset(); + _sentPosition = getObjectPosition() - ObjectMotionState::getWorldOffset(); btTransform worldTrans; worldTrans.setOrigin(glmToBullet(_sentPosition)); - _sentRotation = _entity->getRotation(); + _sentRotation = getObjectRotation(); worldTrans.setRotation(glmToBullet(_sentRotation)); _body->setWorldTransform(worldTrans); } if (flags & EntityItem::DIRTY_VELOCITY) { - updateObjectVelocities(); + updateBodyVelocities(); + } + _sentStep = step; + + if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { + _body->activate(); } - _sentFrame = frame; } - // TODO: entity support for friction and restitution - //_restitution = _entity->getRestitution(); - _body->setRestitution(_restitution); - //_friction = _entity->getFriction(); - _body->setFriction(_friction); - - _linearDamping = _entity->getDamping(); - _angularDamping = _entity->getAngularDamping(); - _body->setDamping(_linearDamping, _angularDamping); + if (flags & EntityItem::DIRTY_MATERIAL) { + updateBodyMaterialProperties(); + } if (flags & EntityItem::DIRTY_MASS) { - float mass = _entity->computeMass(); + ShapeInfo shapeInfo; + _entity->computeShapeInfo(shapeInfo); + float mass = computeObjectMass(shapeInfo); btVector3 inertia(0.0f, 0.0f, 0.0f); _body->getCollisionShape()->calculateLocalInertia(mass, inertia); _body->setMassProps(mass, inertia); _body->updateInertiaTensor(); } - _body->activate(); -}; +} -void EntityMotionState::updateObjectVelocities() { +void EntityMotionState::updateBodyMaterialProperties() { + _body->setRestitution(getObjectRestitution()); + _body->setFriction(getObjectFriction()); + _body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f))); +} + +void EntityMotionState::updateBodyVelocities() { if (_body) { - _sentVelocity = _entity->getVelocity(); - setVelocity(_sentVelocity); + _sentVelocity = getObjectLinearVelocity(); + setBodyVelocity(_sentVelocity); - _sentAngularVelocity = _entity->getAngularVelocity(); - setAngularVelocity(_sentAngularVelocity); + _sentAngularVelocity = getObjectAngularVelocity(); + setBodyAngularVelocity(_sentAngularVelocity); - _sentAcceleration = _entity->getGravity(); - setGravity(_sentAcceleration); + _sentGravity = getObjectGravity(); + setBodyGravity(_sentGravity); _body->setActivationState(ACTIVE_TAG); } } -void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) { +void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) { if (_entity->isReadyToComputeShape()) { _entity->computeShapeInfo(shapeInfo); } } -float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const { +float EntityMotionState::computeObjectMass(const ShapeInfo& shapeInfo) const { return _entity->computeMass(); } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) { +bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) { + if (!ObjectMotionState::shouldSendUpdate(simulationFrame)) { + return false; + } + + if (getShouldClaimSimulationOwnership()) { + return true; + } + + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + const QUuid& simulatorID = _entity->getSimulatorID(); + + if (simulatorID != myNodeID) { + // some other Node owns the simulating of this, so don't broadcast the results of local simulation. + return false; + } + + return true; +} + +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { if (!_entity->isKnownID()) { return; // never update entities that are unknown } if (_outgoingPacketFlags) { EntityItemProperties properties = _entity->getProperties(); + float gravityLength = glm::length(_entity->getGravity()); + float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); + if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) { + // acceleration measured during the most recent simulation step was close to gravity. + if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) { + // only increment this if we haven't reached the threshold yet. this is to avoid + // overflowing the counter. + incrementAccelerationNearlyGravityCount(); + } + } else { + // acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter + resetAccelerationNearlyGravityCount(); + } + + // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let + // the entity server's estimates include gravity. + if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) { + _entity->setAcceleration(_entity->getGravity()); + } else { + _entity->setAcceleration(glm::vec3(0.0f)); + } + if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) { btTransform worldTrans = _body->getWorldTransform(); _sentPosition = bulletToGLM(worldTrans.getOrigin()); @@ -194,7 +264,10 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _sentRotation = bulletToGLM(worldTrans.getRotation()); properties.setRotation(_sentRotation); } - + + bool zeroSpeed = true; + bool zeroSpin = true; + if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) { if (_body->isActive()) { _sentVelocity = bulletToGLM(_body->getLinearVelocity()); @@ -202,12 +275,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ // if the speeds are very small we zero them out const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec - bool zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); + zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); if (zeroSpeed) { _sentVelocity = glm::vec3(0.0f); } const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec - bool zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED; + zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED; if (zeroSpin) { _sentAngularVelocity = glm::vec3(0.0f); } @@ -218,11 +291,30 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _sentMoving = false; } properties.setVelocity(_sentVelocity); - _sentAcceleration = bulletToGLM(_body->getGravity()); - properties.setGravity(_sentAcceleration); + _sentGravity = _entity->getGravity(); + properties.setGravity(_entity->getGravity()); + _sentAcceleration = _entity->getAcceleration(); + properties.setAcceleration(_sentAcceleration); properties.setAngularVelocity(_sentAngularVelocity); } + auto nodeList = DependencyManager::get(); + QUuid myNodeID = nodeList->getSessionUUID(); + QUuid simulatorID = _entity->getSimulatorID(); + + if (getShouldClaimSimulationOwnership()) { + properties.setSimulatorID(myNodeID); + setShouldClaimSimulationOwnership(false); + } else if (simulatorID == myNodeID && zeroSpeed && zeroSpin) { + // we are the simulator and the entity has stopped. give up "simulator" status + _entity->setSimulatorID(QUuid()); + properties.setSimulatorID(QUuid()); + } else if (simulatorID == myNodeID && !_body->isActive()) { + // it's not active. don't keep simulation ownership. + _entity->setSimulatorID(QUuid()); + properties.setSimulatorID(QUuid()); + } + // RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit. if (_sentMoving) { _numNonMovingUpdates = 0; @@ -231,7 +323,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ } if (_numNonMovingUpdates <= 1) { // we only update lastEdited when we're sending new physics data - // (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data) + // (i.e. NOT when we just simulate the positions forward, nor when we resend non-moving data) // NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly quint64 lastSimulated = _entity->getLastSimulated(); _entity->setLastEdited(lastSimulated); @@ -240,7 +332,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ #ifdef WANT_DEBUG quint64 now = usecTimestampNow(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; + qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() + << "---------------------------------------------"; qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); #endif //def WANT_DEBUG @@ -254,6 +347,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ #ifdef WANT_DEBUG qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif + entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); } else { #ifdef WANT_DEBUG @@ -264,7 +358,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ // The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them // to the full set. These flags may be momentarily cleared by incoming external changes. _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; - _sentFrame = frame; + _sentStep = step; } } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 7214626fc4..98d05a0420 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -31,12 +31,11 @@ public: static void setOutgoingEntityList(QSet* list); static void enqueueOutgoingEntity(EntityItem* entity); - EntityMotionState() = delete; // prevent compiler from making default ctor EntityMotionState(EntityItem* item); virtual ~EntityMotionState(); /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem - virtual MotionType computeMotionType() const; + virtual MotionType computeObjectMotionType() const; virtual void updateKinematicState(uint32_t substep); virtual void stepKinematicSimulation(quint64 now); @@ -50,21 +49,43 @@ public: virtual void setWorldTransform(const btTransform& worldTrans); // these relay incoming values to the RigidBody - virtual void updateObjectEasy(uint32_t flags, uint32_t frame); - virtual void updateObjectVelocities(); + virtual void updateBodyEasy(uint32_t flags, uint32_t step); + virtual void updateBodyMaterialProperties(); + virtual void updateBodyVelocities(); - virtual void computeShapeInfo(ShapeInfo& shapeInfo); - virtual float computeMass(const ShapeInfo& shapeInfo) const; + virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo); + virtual float computeObjectMass(const ShapeInfo& shapeInfo) const; - virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame); + virtual bool shouldSendUpdate(uint32_t simulationFrame); + virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); virtual uint32_t getIncomingDirtyFlags() const; virtual void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); } - EntityItem* getEntity() const { return _entity; } + void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; } + void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } + quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; } + + virtual EntityItem* getEntity() const { return _entity; } + virtual void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; } + virtual bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; } + + virtual float getObjectRestitution() const { return _entity->getRestitution(); } + virtual float getObjectFriction() const { return _entity->getFriction(); } + virtual float getObjectLinearDamping() const { return _entity->getDamping(); } + virtual float getObjectAngularDamping() const { return _entity->getAngularDamping(); } + + virtual const glm::vec3& getObjectPosition() const { return _entity->getPosition(); } + virtual const glm::quat& getObjectRotation() const { return _entity->getRotation(); } + virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); } + virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } + virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); } protected: EntityItem* _entity; + quint8 _accelerationNearlyGravityCount; + bool _shouldClaimSimulationOwnership; + quint32 _movingStepsWithoutSimulationOwner; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index c9f416cc37..d8eb86f0b4 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -22,7 +22,7 @@ const float MAX_FRICTION = 10.0f; const float DEFAULT_RESTITUTION = 0.5f; -// origin of physics simulation in world frame +// origin of physics simulation in world-frame glm::vec3 _worldOffset(0.0f); // static @@ -35,23 +35,29 @@ const glm::vec3& ObjectMotionState::getWorldOffset() { return _worldOffset; } +// static +uint32_t _worldSimulationStep = 0; +void ObjectMotionState::setWorldSimulationStep(uint32_t step) { + assert(step > _worldSimulationStep); + _worldSimulationStep = step; +} ObjectMotionState::ObjectMotionState() : - _friction(DEFAULT_FRICTION), - _restitution(DEFAULT_RESTITUTION), - _linearDamping(0.0f), - _angularDamping(0.0f), _motionType(MOTION_TYPE_STATIC), _body(NULL), _sentMoving(false), _numNonMovingUpdates(0), _outgoingPacketFlags(DIRTY_PHYSICS_FLAGS), - _sentFrame(0), + _sentStep(0), _sentPosition(0.0f), _sentRotation(), _sentVelocity(0.0f), _sentAngularVelocity(0.0f), - _sentAcceleration(0.0f) { + _sentGravity(0.0f), + _sentAcceleration(0.0f), + _lastSimulationStep(0), + _lastVelocity(0.0f), + _measuredAcceleration(0.0f) { } ObjectMotionState::~ObjectMotionState() { @@ -59,31 +65,36 @@ ObjectMotionState::~ObjectMotionState() { assert(_body == NULL); } -void ObjectMotionState::setFriction(float friction) { - _friction = btMax(btMin(fabsf(friction), MAX_FRICTION), 0.0f); +void ObjectMotionState::measureBodyAcceleration() { + // try to manually measure the true acceleration of the object + uint32_t numSubsteps = _worldSimulationStep - _lastSimulationStep; + if (numSubsteps > 0) { + float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP); + float invDt = 1.0f / dt; + _lastSimulationStep = _worldSimulationStep; + + // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt + // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt + glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity()); + _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; + _lastVelocity = velocity; + } } -void ObjectMotionState::setRestitution(float restitution) { - _restitution = btMax(btMin(fabsf(restitution), 1.0f), 0.0f); +void ObjectMotionState::resetMeasuredBodyAcceleration() { + _lastSimulationStep = _worldSimulationStep; + _lastVelocity = bulletToGLM(_body->getLinearVelocity()); } -void ObjectMotionState::setLinearDamping(float damping) { - _linearDamping = btMax(btMin(fabsf(damping), 1.0f), 0.0f); -} - -void ObjectMotionState::setAngularDamping(float damping) { - _angularDamping = btMax(btMin(fabsf(damping), 1.0f), 0.0f); -} - -void ObjectMotionState::setVelocity(const glm::vec3& velocity) const { +void ObjectMotionState::setBodyVelocity(const glm::vec3& velocity) const { _body->setLinearVelocity(glmToBullet(velocity)); } -void ObjectMotionState::setAngularVelocity(const glm::vec3& velocity) const { +void ObjectMotionState::setBodyAngularVelocity(const glm::vec3& velocity) const { _body->setAngularVelocity(glmToBullet(velocity)); } -void ObjectMotionState::setGravity(const glm::vec3& gravity) const { +void ObjectMotionState::setBodyGravity(const glm::vec3& gravity) const { _body->setGravity(glmToBullet(gravity)); } @@ -103,15 +114,16 @@ bool ObjectMotionState::doesNotNeedToSendUpdate() const { return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES; } -bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { +bool ObjectMotionState::shouldSendUpdate(uint32_t simulationStep) { assert(_body); - // if we've never checked before, our _sentFrame will be 0, and we need to initialize our state - if (_sentFrame == 0) { - _sentPosition = bulletToGLM(_body->getWorldTransform().getOrigin()); + // if we've never checked before, our _sentStep will be 0, and we need to initialize our state + if (_sentStep == 0) { + btTransform xform = _body->getWorldTransform(); + _sentPosition = bulletToGLM(xform.getOrigin()); + _sentRotation = bulletToGLM(xform.getRotation()); _sentVelocity = bulletToGLM(_body->getLinearVelocity()); - _sentRotation = bulletToGLM(_body->getWorldTransform().getRotation()); _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); - _sentFrame = simulationFrame; + _sentStep = simulationStep; return false; } @@ -121,9 +133,9 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { glm::vec3 wasAngularVelocity = _sentAngularVelocity; #endif - int numFrames = simulationFrame - _sentFrame; - float dt = (float)(numFrames) * PHYSICS_ENGINE_FIXED_SUBSTEP; - _sentFrame = simulationFrame; + int numSteps = simulationStep - _sentStep; + float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; + _sentStep = simulationStep; bool isActive = _body->isActive(); if (!isActive) { @@ -143,13 +155,13 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. - // NOTE: math in done the simulation-frame, which is NOT necessarily the same as the world-frame + // NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame // due to _worldOffset. // compute position error if (glm::length2(_sentVelocity) > 0.0f) { _sentVelocity += _sentAcceleration * dt; - _sentVelocity *= powf(1.0f - _linearDamping, dt); + _sentVelocity *= powf(1.0f - _body->getLinearDamping(), dt); _sentPosition += dt * _sentVelocity; } @@ -174,12 +186,12 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { if (glm::length2(_sentAngularVelocity) > 0.0f) { // compute rotation error - float attenuation = powf(1.0f - _angularDamping, dt); + float attenuation = powf(1.0f - _body->getAngularDamping(), dt); _sentAngularVelocity *= attenuation; // Bullet caps the effective rotation velocity inside its rotation integration step, therefore // we must integrate with the same algorithm and timestep in order achieve similar results. - for (int i = 0; i < numFrames; ++i) { + for (int i = 0; i < numSteps; ++i) { _sentRotation = glm::normalize(computeBulletRotationStep(_sentAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _sentRotation); } } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index fb402a178d..7c00eedd09 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -36,7 +36,7 @@ enum MotionStateType { // and re-added to the physics engine and "easy" which just updates the body properties. const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY | - EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP); + EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | EntityItem::DIRTY_MATERIAL); // These are the set of incoming flags that the PhysicsEngine needs to hear about: const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS; @@ -47,6 +47,8 @@ const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | Entit class OctreeEditPacketSender; +extern const int MAX_NUM_NON_MOVING_UPDATES; + class ObjectMotionState : public btMotionState { public: // The WorldOffset is used to keep the positions of objects in the simulation near the origin, to @@ -57,27 +59,30 @@ public: static void setWorldOffset(const glm::vec3& offset); static const glm::vec3& getWorldOffset(); + // The WorldSimulationStep is a cached copy of number of SubSteps of the simulation, used for local time measurements. + static void setWorldSimulationStep(uint32_t step); + ObjectMotionState(); ~ObjectMotionState(); + void measureBodyAcceleration(); + void resetMeasuredBodyAcceleration(); + // An EASY update does not require the object to be removed and then reinserted into the PhysicsEngine - virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0; - virtual void updateObjectVelocities() = 0; + virtual void updateBodyEasy(uint32_t flags, uint32_t frame) = 0; + + virtual void updateBodyMaterialProperties() = 0; + virtual void updateBodyVelocities() = 0; MotionStateType getType() const { return _type; } virtual MotionType getMotionType() const { return _motionType; } - virtual void computeShapeInfo(ShapeInfo& info) = 0; - virtual float computeMass(const ShapeInfo& shapeInfo) const = 0; + virtual void computeObjectShapeInfo(ShapeInfo& info) = 0; + virtual float computeObjectMass(const ShapeInfo& shapeInfo) const = 0; - void setFriction(float friction); - void setRestitution(float restitution); - void setLinearDamping(float damping); - void setAngularDamping(float damping); - - void setVelocity(const glm::vec3& velocity) const; - void setAngularVelocity(const glm::vec3& velocity) const; - void setGravity(const glm::vec3& gravity) const; + void setBodyVelocity(const glm::vec3& velocity) const; + void setBodyAngularVelocity(const glm::vec3& velocity) const; + void setBodyGravity(const glm::vec3& gravity) const; void getVelocity(glm::vec3& velocityOut) const; void getAngularVelocity(glm::vec3& angularVelocityOut) const; @@ -87,10 +92,10 @@ public: void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; } bool doesNotNeedToSendUpdate() const; - virtual bool shouldSendUpdate(uint32_t simulationFrame); + virtual bool shouldSendUpdate(uint32_t simulationStep); virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0; - virtual MotionType computeMotionType() const = 0; + virtual MotionType computeObjectMotionType() const = 0; virtual void updateKinematicState(uint32_t substep) = 0; @@ -104,18 +109,32 @@ public: virtual bool isMoving() const = 0; friend class PhysicsEngine; + + // these are here so we can call into EntityMotionObject with a base-class pointer + virtual EntityItem* getEntity() const { return NULL; } + virtual void setShouldClaimSimulationOwnership(bool value) { } + virtual bool getShouldClaimSimulationOwnership() { return false; } + + // These pure virtual methods must be implemented for each MotionState type + // and make it possible to implement more complicated methods in this base class. + + virtual float getObjectRestitution() const = 0; + virtual float getObjectFriction() const = 0; + virtual float getObjectLinearDamping() const = 0; + virtual float getObjectAngularDamping() const = 0; + + virtual const glm::vec3& getObjectPosition() const = 0; + virtual const glm::quat& getObjectRotation() const = 0; + virtual const glm::vec3& getObjectLinearVelocity() const = 0; + virtual const glm::vec3& getObjectAngularVelocity() const = 0; + virtual const glm::vec3& getObjectGravity() const = 0; + protected: void setRigidBody(btRigidBody* body); - MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN; + MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN; // type of MotionState - // TODO: move these materials properties outside of ObjectMotionState - float _friction; - float _restitution; - float _linearDamping; - float _angularDamping; - - MotionType _motionType; + MotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC btRigidBody* _body; @@ -126,12 +145,17 @@ protected: int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects uint32_t _outgoingPacketFlags; - uint32_t _sentFrame; + uint32_t _sentStep; glm::vec3 _sentPosition; // in simulation-frame (not world-frame) glm::quat _sentRotation;; glm::vec3 _sentVelocity; glm::vec3 _sentAngularVelocity; // radians per second + glm::vec3 _sentGravity; glm::vec3 _sentAcceleration; + + uint32_t _lastSimulationStep; + glm::vec3 _lastVelocity; + glm::vec3 _measuredAcceleration; }; #endif // hifi_ObjectMotionState_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 50f52a7efc..6f60fea013 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -15,6 +15,7 @@ #include "ShapeInfoUtil.h" #include "PhysicsHelpers.h" #include "ThreadSafeDynamicsWorld.h" +#include "PhysicsLogging.h" static uint32_t _numSubsteps; @@ -23,8 +24,9 @@ uint32_t PhysicsEngine::getNumSubsteps() { return _numSubsteps; } -PhysicsEngine::PhysicsEngine(const glm::vec3& offset) - : _originOffset(offset) { +PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : + _originOffset(offset), + _characterController(NULL) { } PhysicsEngine::~PhysicsEngine() { @@ -118,8 +120,10 @@ void PhysicsEngine::entityChangedInternal(EntityItem* entity) { assert(entity); void* physicsInfo = entity->getPhysicsInfo(); if (physicsInfo) { - ObjectMotionState* motionState = static_cast(physicsInfo); - _incomingChanges.insert(motionState); + if ((entity->getDirtyFlags() & (HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS)) > 0) { + ObjectMotionState* motionState = static_cast(physicsInfo); + _incomingChanges.insert(motionState); + } } else { // try to add this entity again (maybe something changed such that it will work this time) addEntity(entity); @@ -169,9 +173,9 @@ void PhysicsEngine::relayIncomingChangesToSimulation() { if (flags & HARD_DIRTY_PHYSICS_FLAGS) { // a HARD update requires the body be pulled out of physics engine, changed, then reinserted // but it also handles all EASY changes - bool success = updateObjectHard(body, motionState, flags); + bool success = updateBodyHard(body, motionState, flags); if (!success) { - // NOTE: since updateObjectHard() failed we know that motionState has been removed + // NOTE: since updateBodyHard() failed we know that motionState has been removed // from simulation and body has been deleted. Depending on what else has changed // we might need to remove motionState altogether... if (flags & EntityItem::DIRTY_VELOCITY) { @@ -191,7 +195,10 @@ void PhysicsEngine::relayIncomingChangesToSimulation() { } else if (flags) { // an EASY update does NOT require that the body be pulled out of physics engine // hence the MotionState has all the knowledge and authority to perform the update. - motionState->updateObjectEasy(flags, _numSubsteps); + motionState->updateBodyEasy(flags, _numSubsteps); + } + if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { + motionState->resetMeasuredBodyAcceleration(); } } else { // the only way we should ever get here (motionState exists but no body) is when the object @@ -201,7 +208,7 @@ void PhysicsEngine::relayIncomingChangesToSimulation() { // it is possible that the changes are such that the object can now be added to the physical simulation if (flags & EntityItem::DIRTY_SHAPE) { ShapeInfo shapeInfo; - motionState->computeShapeInfo(shapeInfo); + motionState->computeObjectShapeInfo(shapeInfo); btCollisionShape* shape = _shapeManager.getShape(shapeInfo); if (shape) { addObject(shapeInfo, shape, motionState); @@ -288,77 +295,73 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) { } void PhysicsEngine::stepSimulation() { - { - lock(); - CProfileManager::Reset(); - BT_PROFILE("stepSimulation"); - // NOTE: the grand order of operations is: - // (1) pull incoming changes - // (2) step simulation - // (3) synchronize outgoing motion states - // (4) send outgoing packets + lock(); + CProfileManager::Reset(); + BT_PROFILE("stepSimulation"); + // NOTE: the grand order of operations is: + // (1) pull incoming changes + // (2) step simulation + // (3) synchronize outgoing motion states + // (4) send outgoing packets - // This is step (1) pull incoming changes - relayIncomingChangesToSimulation(); - - const int MAX_NUM_SUBSTEPS = 4; - const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP; - float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds()); - _clock.reset(); - float timeStep = btMin(dt, MAX_TIMESTEP); - - // TODO: move character->preSimulation() into relayIncomingChanges - if (_characterController) { - if (_characterController->needsRemoval()) { - _characterController->setDynamicsWorld(NULL); - } - _characterController->updateShapeIfNecessary(); - if (_characterController->needsAddition()) { - _characterController->setDynamicsWorld(_dynamicsWorld); - } - _characterController->preSimulation(timeStep); + // This is step (1) pull incoming changes + relayIncomingChangesToSimulation(); + + const int MAX_NUM_SUBSTEPS = 4; + const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP; + float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds()); + _clock.reset(); + float timeStep = btMin(dt, MAX_TIMESTEP); + + // TODO: move character->preSimulation() into relayIncomingChanges + if (_characterController) { + if (_characterController->needsRemoval()) { + _characterController->setDynamicsWorld(NULL); } - - // This is step (2) step simulation - int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); - _numSubsteps += (uint32_t)numSubsteps; - stepNonPhysicalKinematics(usecTimestampNow()); - unlock(); - - // TODO: make all of this harvest stuff into one function: relayOutgoingChanges() - if (numSubsteps > 0) { - BT_PROFILE("postSimulation"); - // This is step (3) which is done outside of stepSimulation() so we can lock _entityTree. - // - // Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree - // to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this - // PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own - // lock on the tree before we re-lock ourselves. - // - // TODO: untangle these lock sequences. - _entityTree->lockForWrite(); - lock(); - _dynamicsWorld->synchronizeMotionStates(); - - if (_characterController) { - _characterController->postSimulation(); - } - - unlock(); - _entityTree->unlock(); - - computeCollisionEvents(); + _characterController->updateShapeIfNecessary(); + if (_characterController->needsAddition()) { + _characterController->setDynamicsWorld(_dynamicsWorld); } + _characterController->preSimulation(timeStep); } - if (_dumpNextStats) { - _dumpNextStats = false; - CProfileManager::dumpAll(); + + // This is step (2) step simulation + int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); + _numSubsteps += (uint32_t)numSubsteps; + stepNonPhysicalKinematics(usecTimestampNow()); + unlock(); + + // TODO: make all of this harvest stuff into one function: relayOutgoingChanges() + if (numSubsteps > 0) { + BT_PROFILE("postSimulation"); + // This is step (3) which is done outside of stepSimulation() so we can lock _entityTree. + // + // Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree + // to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this + // PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own + // lock on the tree before we re-lock ourselves. + // + // TODO: untangle these lock sequences. + ObjectMotionState::setWorldSimulationStep(_numSubsteps); + _entityTree->lockForWrite(); + lock(); + _dynamicsWorld->synchronizeMotionStates(); + + if (_characterController) { + _characterController->postSimulation(); + } + + computeCollisionEvents(); + + unlock(); + _entityTree->unlock(); } } void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) { BT_PROFILE("nonPhysicalKinematics"); QSet::iterator stateItr = _nonPhysicalKinematicObjects.begin(); + // TODO?: need to occasionally scan for stopped non-physical kinematics objects while (stateItr != _nonPhysicalKinematicObjects.end()) { ObjectMotionState* motionState = *stateItr; motionState->stepKinematicSimulation(now); @@ -366,10 +369,49 @@ void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) { } } -// TODO?: need to occasionally scan for stopped non-physical kinematics objects + +void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { + assert(objectA); + assert(objectB); + + auto nodeList = DependencyManager::get(); + QUuid myNodeID = nodeList->getSessionUUID(); + + if (myNodeID.isNull()) { + return; + } + + const btCollisionObject* characterCollisionObject = + _characterController ? _characterController->getCollisionObject() : NULL; + + ObjectMotionState* a = static_cast(objectA->getUserPointer()); + ObjectMotionState* b = static_cast(objectB->getUserPointer()); + EntityItem* entityA = a ? a->getEntity() : NULL; + EntityItem* entityB = b ? b->getEntity() : NULL; + bool aIsDynamic = entityA && !objectA->isStaticOrKinematicObject(); + bool bIsDynamic = entityB && !objectB->isStaticOrKinematicObject(); + + // collisions cause infectious spread of simulation-ownership. we also attempt to take + // ownership of anything that collides with our avatar. + if ((aIsDynamic && (entityA->getSimulatorID() == myNodeID)) || + // (a && a->getShouldClaimSimulationOwnership()) || + (objectA == characterCollisionObject)) { + if (bIsDynamic) { + b->setShouldClaimSimulationOwnership(true); + } + } else if ((bIsDynamic && (entityB->getSimulatorID() == myNodeID)) || + // (b && b->getShouldClaimSimulationOwnership()) || + (objectB == characterCollisionObject)) { + if (aIsDynamic) { + a->setShouldClaimSimulationOwnership(true); + } + } +} + void PhysicsEngine::computeCollisionEvents() { BT_PROFILE("computeCollisionEvents"); + // update all contacts every frame int numManifolds = _collisionDispatcher->getNumManifolds(); for (int i = 0; i < numManifolds; ++i) { @@ -385,34 +427,23 @@ void PhysicsEngine::computeCollisionEvents() { // which will eventually trigger a CONTACT_EVENT_TYPE_END continue; } - - void* a = objectA->getUserPointer(); - void* b = objectB->getUserPointer(); + + ObjectMotionState* a = static_cast(objectA->getUserPointer()); + ObjectMotionState* b = static_cast(objectB->getUserPointer()); if (a || b) { // the manifold has up to 4 distinct points, but only extract info from the first _contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset); } + + doOwnershipInfection(objectA, objectB); } } - // We harvest collision callbacks every few frames, which contributes the following effects: - // - // (1) There is a maximum collision callback rate per pair: substep_rate / SUBSTEPS_PER_COLLIION_FRAME - // (2) END/START cycles shorter than SUBSTEPS_PER_COLLIION_FRAME will be filtered out - // (3) There is variable lag between when the contact actually starts and when it is reported, - // up to SUBSTEPS_PER_COLLIION_FRAME * time_per_substep - // - const uint32_t SUBSTEPS_PER_COLLISION_FRAME = 2; - if (_numSubsteps - _numContactFrames * SUBSTEPS_PER_COLLISION_FRAME < SUBSTEPS_PER_COLLISION_FRAME) { - // we don't harvest collision callbacks every frame - // this sets a maximum callback-per-contact rate - // and also filters out END/START events that happen on shorter timescales - return; - } + const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10; - ++_numContactFrames; // scan known contacts and trigger events ContactMap::iterator contactItr = _contactMap.begin(); + while (contactItr != _contactMap.end()) { ObjectMotionState* A = static_cast(contactItr->first._a); ObjectMotionState* B = static_cast(contactItr->first._b); @@ -420,21 +451,23 @@ void PhysicsEngine::computeCollisionEvents() { // TODO: make triggering these events clean and efficient. The code at this context shouldn't // have to figure out what kind of object (entity, avatar, etc) these are in order to properly // emit a collision event. - if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) { - EntityItemID idA = static_cast(A)->getEntity()->getEntityItemID(); - EntityItemID idB; - if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) { - idB = static_cast(B)->getEntity()->getEntityItemID(); - } - emit entityCollisionWithEntity(idA, idB, contactItr->second); - } else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) { - EntityItemID idA; - EntityItemID idB = static_cast(B)->getEntity()->getEntityItemID(); - emit entityCollisionWithEntity(idA, idB, contactItr->second); - } - // TODO: enable scripts to filter based on contact event type ContactEventType type = contactItr->second.computeType(_numContactFrames); + if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0){ + if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) { + EntityItemID idA = static_cast(A)->getEntity()->getEntityItemID(); + EntityItemID idB; + if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) { + idB = static_cast(B)->getEntity()->getEntityItemID(); + } + emit entityCollisionWithEntity(idA, idB, contactItr->second); + } else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) { + EntityItemID idA; + EntityItemID idB = static_cast(B)->getEntity()->getEntityItemID(); + emit entityCollisionWithEntity(idA, idB, contactItr->second); + } + } + if (type == CONTACT_EVENT_TYPE_END) { ContactMap::iterator iterToDelete = contactItr; ++contactItr; @@ -443,6 +476,14 @@ void PhysicsEngine::computeCollisionEvents() { ++contactItr; } } + ++_numContactFrames; +} + +void PhysicsEngine::dumpStatsIfNecessary() { + if (_dumpNextStats) { + _dumpNextStats = false; + CProfileManager::dumpAll(); + } } // Bullet collision flags are as follows: @@ -461,7 +502,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap btVector3 inertia(0.0f, 0.0f, 0.0f); float mass = 0.0f; btRigidBody* body = NULL; - switch(motionState->computeMotionType()) { + switch(motionState->computeObjectMotionType()) { case MOTION_TYPE_KINEMATIC: { body = new btRigidBody(mass, motionState, shape, inertia); body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); @@ -474,13 +515,13 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap break; } case MOTION_TYPE_DYNAMIC: { - mass = motionState->computeMass(shapeInfo); + mass = motionState->computeObjectMass(shapeInfo); shape->calculateLocalInertia(mass, inertia); body = new btRigidBody(mass, motionState, shape, inertia); body->updateInertiaTensor(); motionState->setRigidBody(body); motionState->setKinematic(false, _numSubsteps); - motionState->updateObjectVelocities(); + motionState->updateBodyVelocities(); // NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds. // (the 2 seconds is determined by: static btRigidBody::gDeactivationTime const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec @@ -503,15 +544,59 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap } } body->setFlags(BT_DISABLE_WORLD_GRAVITY); - body->setRestitution(motionState->_restitution); - body->setFriction(motionState->_friction); - body->setDamping(motionState->_linearDamping, motionState->_angularDamping); + motionState->updateBodyMaterialProperties(); + _dynamicsWorld->addRigidBody(body); + motionState->resetMeasuredBodyAcceleration(); +} + +void PhysicsEngine::bump(EntityItem* bumpEntity) { + // If this node is doing something like deleting an entity, scan for contacts involving the + // entity. For each found, flag the other entity involved as being simulated by this node. + lock(); + int numManifolds = _collisionDispatcher->getNumManifolds(); + for (int i = 0; i < numManifolds; ++i) { + btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i); + if (contactManifold->getNumContacts() > 0) { + const btCollisionObject* objectA = static_cast(contactManifold->getBody0()); + const btCollisionObject* objectB = static_cast(contactManifold->getBody1()); + if (objectA && objectB) { + void* a = objectA->getUserPointer(); + void* b = objectB->getUserPointer(); + if (a && b) { + EntityMotionState* entityMotionStateA = static_cast(a); + EntityMotionState* entityMotionStateB = static_cast(b); + EntityItem* entityA = entityMotionStateA ? entityMotionStateA->getEntity() : NULL; + EntityItem* entityB = entityMotionStateB ? entityMotionStateB->getEntity() : NULL; + if (entityA && entityB) { + if (entityA == bumpEntity) { + entityMotionStateB->setShouldClaimSimulationOwnership(true); + if (!objectB->isActive()) { + objectB->setActivationState(ACTIVE_TAG); + } + } + if (entityB == bumpEntity) { + entityMotionStateA->setShouldClaimSimulationOwnership(true); + if (!objectA->isActive()) { + objectA->setActivationState(ACTIVE_TAG); + } + } + } + } + } + } + } + unlock(); } void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) { assert(motionState); btRigidBody* body = motionState->getRigidBody(); + + // wake up anything touching this object + EntityItem* entityItem = motionState ? motionState->getEntity() : NULL; + bump(entityItem); + if (body) { const btCollisionShape* shape = body->getCollisionShape(); _dynamicsWorld->removeRigidBody(body); @@ -526,8 +611,8 @@ void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) { } // private -bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { - MotionType newType = motionState->computeMotionType(); +bool PhysicsEngine::updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { + MotionType newType = motionState->computeObjectMotionType(); // pull body out of physics engine _dynamicsWorld->removeRigidBody(body); @@ -539,7 +624,7 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio // get new shape btCollisionShape* oldShape = body->getCollisionShape(); ShapeInfo shapeInfo; - motionState->computeShapeInfo(shapeInfo); + motionState->computeObjectShapeInfo(shapeInfo); btCollisionShape* newShape = _shapeManager.getShape(shapeInfo); if (!newShape) { // FAIL! we are unable to support these changes! @@ -558,7 +643,7 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio _shapeManager.releaseShape(oldShape); // compute mass properties - float mass = motionState->computeMass(shapeInfo); + float mass = motionState->computeObjectMass(shapeInfo); btVector3 inertia(0.0f, 0.0f, 0.0f); body->getCollisionShape()->calculateLocalInertia(mass, inertia); body->setMassProps(mass, inertia); @@ -571,7 +656,7 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio } bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS; if (easyUpdate) { - motionState->updateObjectEasy(flags, _numSubsteps); + motionState->updateBodyEasy(flags, _numSubsteps); } // update the motion parameters @@ -593,8 +678,8 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio if (! (flags & EntityItem::DIRTY_MASS)) { // always update mass properties when going dynamic (unless it's already been done above) ShapeInfo shapeInfo; - motionState->computeShapeInfo(shapeInfo); - float mass = motionState->computeMass(shapeInfo); + motionState->computeObjectShapeInfo(shapeInfo); + float mass = motionState->computeObjectMass(shapeInfo); btVector3 inertia(0.0f, 0.0f, 0.0f); body->getCollisionShape()->calculateLocalInertia(mass, inertia); body->setMassProps(mass, inertia); diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 01717be175..4f931d939a 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -39,8 +39,8 @@ public: ContactKey(void* a, void* b) : _a(a), _b(b) {} bool operator<(const ContactKey& other) const { return _a < other._a || (_a == other._a && _b < other._b); } bool operator==(const ContactKey& other) const { return _a == other._a && _b == other._b; } - void* _a; - void* _b; + void* _a; // EntityMotionState pointer + void* _b; // EntityMotionState pointer }; typedef std::map ContactMap; @@ -68,9 +68,10 @@ public: void stepSimulation(); void stepNonPhysicalKinematics(const quint64& now); - void computeCollisionEvents(); + void dumpStatsIfNecessary(); + /// \param offset position of simulation origin in domain-frame void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; } @@ -88,14 +89,18 @@ public: void dumpNextStats() { _dumpNextStats = true; } + void bump(EntityItem* bumpEntity); + private: /// \param motionState pointer to Object's MotionState void removeObjectFromBullet(ObjectMotionState* motionState); void removeContacts(ObjectMotionState* motionState); + void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB); + // return 'true' of update was successful - bool updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); + bool updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); btClock _clock; diff --git a/libraries/physics/src/ShapeInfoUtil.cpp b/libraries/physics/src/ShapeInfoUtil.cpp index 8900c5a0dc..c6c76a98eb 100644 --- a/libraries/physics/src/ShapeInfoUtil.cpp +++ b/libraries/physics/src/ShapeInfoUtil.cpp @@ -14,107 +14,6 @@ #include "ShapeInfoUtil.h" #include "BulletUtil.h" -int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) { - int bulletShapeType = INVALID_SHAPE_PROXYTYPE; - switch(shapeInfoType) { - case SHAPE_TYPE_BOX: - bulletShapeType = BOX_SHAPE_PROXYTYPE; - break; - case SHAPE_TYPE_SPHERE: - bulletShapeType = SPHERE_SHAPE_PROXYTYPE; - break; - case SHAPE_TYPE_CAPSULE_Y: - bulletShapeType = CAPSULE_SHAPE_PROXYTYPE; - break; - case SHAPE_TYPE_CONVEX_HULL: - bulletShapeType = CONVEX_HULL_SHAPE_PROXYTYPE; - break; - case SHAPE_TYPE_COMPOUND: - bulletShapeType = COMPOUND_SHAPE_PROXYTYPE; - break; - } - return bulletShapeType; -} - -int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) { - int shapeInfoType = SHAPE_TYPE_NONE; - switch(bulletShapeType) { - case BOX_SHAPE_PROXYTYPE: - shapeInfoType = SHAPE_TYPE_BOX; - break; - case SPHERE_SHAPE_PROXYTYPE: - shapeInfoType = SHAPE_TYPE_SPHERE; - break; - case CAPSULE_SHAPE_PROXYTYPE: - shapeInfoType = SHAPE_TYPE_CAPSULE_Y; - break; - case CONVEX_HULL_SHAPE_PROXYTYPE: - shapeInfoType = SHAPE_TYPE_CONVEX_HULL; - break; - case COMPOUND_SHAPE_PROXYTYPE: - shapeInfoType = SHAPE_TYPE_COMPOUND; - break; - } - return shapeInfoType; -} - -void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info) { - if (shape) { - int type = ShapeInfoUtil::fromBulletShapeType(shape->getShapeType()); - switch(type) { - case SHAPE_TYPE_BOX: { - const btBoxShape* boxShape = static_cast(shape); - info.setBox(bulletToGLM(boxShape->getHalfExtentsWithMargin())); - } - break; - case SHAPE_TYPE_SPHERE: { - const btSphereShape* sphereShape = static_cast(shape); - info.setSphere(sphereShape->getRadius()); - } - break; - case SHAPE_TYPE_CONVEX_HULL: { - const btConvexHullShape* convexHullShape = static_cast(shape); - const int numPoints = convexHullShape->getNumPoints(); - const btVector3* btPoints = convexHullShape->getUnscaledPoints(); - QVector> points; - QVector childPoints; - for (int i = 0; i < numPoints; i++) { - glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ()); - childPoints << point; - } - points << childPoints; - info.setConvexHulls(points); - } - break; - case SHAPE_TYPE_COMPOUND: { - const btCompoundShape* compoundShape = static_cast(shape); - const int numChildShapes = compoundShape->getNumChildShapes(); - QVector> points; - for (int i = 0; i < numChildShapes; i ++) { - const btCollisionShape* childShape = compoundShape->getChildShape(i); - const btConvexHullShape* convexHullShape = static_cast(childShape); - const int numPoints = convexHullShape->getNumPoints(); - const btVector3* btPoints = convexHullShape->getUnscaledPoints(); - - QVector childPoints; - for (int j = 0; j < numPoints; j++) { - glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ()); - childPoints << point; - } - points << childPoints; - } - info.setConvexHulls(points); - } - break; - default: { - info.clear(); - } - break; - } - } else { - info.clear(); - } -} btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; @@ -135,33 +34,34 @@ btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) { shape = new btCapsuleShape(radius, height); } break; - case SHAPE_TYPE_CONVEX_HULL: { - auto hull = new btConvexHullShape(); - const QVector>& points = info.getPoints(); - foreach (glm::vec3 point, points[0]) { - btVector3 btPoint(point[0], point[1], point[2]); - hull->addPoint(btPoint, false); - } - hull->recalcLocalAabb(); - shape = hull; - } - break; case SHAPE_TYPE_COMPOUND: { - auto compound = new btCompoundShape(); const QVector>& points = info.getPoints(); - - btTransform trans; - trans.setIdentity(); - foreach (QVector hullPoints, points) { + uint32_t numSubShapes = info.getNumSubShapes(); + if (numSubShapes == 1) { auto hull = new btConvexHullShape(); - foreach (glm::vec3 point, hullPoints) { + const QVector>& points = info.getPoints(); + foreach (glm::vec3 point, points[0]) { btVector3 btPoint(point[0], point[1], point[2]); hull->addPoint(btPoint, false); } hull->recalcLocalAabb(); - compound->addChildShape (trans, hull); + shape = hull; + } else { + assert(numSubShapes > 1); + auto compound = new btCompoundShape(); + btTransform trans; + trans.setIdentity(); + foreach (QVector hullPoints, points) { + auto hull = new btConvexHullShape(); + foreach (glm::vec3 point, hullPoints) { + btVector3 btPoint(point[0], point[1], point[2]); + hull->addPoint(btPoint, false); + } + hull->recalcLocalAabb(); + compound->addChildShape (trans, hull); + } + shape = compound; } - shape = compound; } break; } diff --git a/libraries/physics/src/ShapeInfoUtil.h b/libraries/physics/src/ShapeInfoUtil.h index 9585161440..39a897019c 100644 --- a/libraries/physics/src/ShapeInfoUtil.h +++ b/libraries/physics/src/ShapeInfoUtil.h @@ -19,16 +19,10 @@ // translates between ShapeInfo and btShape +// TODO: rename this to ShapeFactory namespace ShapeInfoUtil { - // XXX is collectInfoFromShape no longer strictly needed? - void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info); - btCollisionShape* createShapeFromInfo(const ShapeInfo& info); - - // TODO? just use bullet shape types everywhere? - int toBulletShapeType(int shapeInfoType); - int fromBulletShapeType(int bulletShapeType); }; #endif // hifi_ShapeInfoUtil_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 2a73f5d28f..dd67c2c73a 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -35,7 +35,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { // Very small or large objects are not supported. float diagonal = 4.0f * glm::length2(info.getHalfExtents()); const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube - const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e6f; // 1000 m cube + //const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e6f; // 1000 m cube if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED /* || diagonal > MAX_SHAPE_DIAGONAL_SQUARED*/ ) { // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; return NULL; @@ -104,11 +104,9 @@ void ShapeManager::collectGarbage() { ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef && shapeRef->refCount == 0) { // if the shape we're about to delete is compound, delete the children first. - auto shapeType = ShapeInfoUtil::fromBulletShapeType(shapeRef->shape->getShapeType()); - if (shapeType == SHAPE_TYPE_COMPOUND) { + if (shapeRef->shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE) { const btCompoundShape* compoundShape = static_cast(shapeRef->shape); const int numChildShapes = compoundShape->getNumChildShapes(); - QVector> points; for (int i = 0; i < numChildShapes; i ++) { const btCollisionShape* childShape = compoundShape->getChildShape(i); delete childShape; diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index caabff44cf..9b2bb87b80 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -6,10 +6,22 @@ AUTOSCRIBE_SHADER_LIB(gpu model) qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") # use setup_hifi_library macro to setup our project and link appropriate Qt modules -setup_hifi_library(Widgets OpenGL Network Script) +setup_hifi_library(Widgets OpenGL Network Qml Quick Script) add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) +if (WIN32) + if (USE_NSIGHT) + # try to find the Nsight package and add it to the build if we find it + find_package(NSIGHT) + if (NSIGHT_FOUND) + include_directories(${NSIGHT_INCLUDE_DIRS}) + add_definitions(-DNSIGHT_FOUND) + target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") + endif () + endif() +endif (WIN32) + link_hifi_libraries(animation fbx shared gpu) \ No newline at end of file diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 40a02a60a4..f58419ec6e 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -12,7 +12,7 @@ // include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL #include -#include +#include #include @@ -107,8 +107,8 @@ void AmbientOcclusionEffect::render() { glBindTexture(GL_TEXTURE_2D, _rotationTextureID); // render with the occlusion shader to the secondary/tertiary buffer - QOpenGLFramebufferObject* freeFBO = DependencyManager::get()->getFreeFramebufferObject(); - freeFBO->bind(); + auto freeFramebuffer = DependencyManager::get()->getFreeFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(freeFramebuffer)); float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; @@ -118,9 +118,10 @@ void AmbientOcclusionEffect::render() { glGetIntegerv(GL_VIEWPORT, viewport); const int VIEWPORT_X_INDEX = 0; const int VIEWPORT_WIDTH_INDEX = 2; - QOpenGLFramebufferObject* primaryFBO = DependencyManager::get()->getPrimaryFramebufferObject(); - float sMin = viewport[VIEWPORT_X_INDEX] / (float)primaryFBO->width(); - float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)primaryFBO->width(); + + auto framebufferSize = DependencyManager::get()->getFrameBufferSize(); + float sMin = viewport[VIEWPORT_X_INDEX] / (float)framebufferSize.width(); + float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)framebufferSize.width(); _occlusionProgram->bind(); _occlusionProgram->setUniformValue(_nearLocation, nearVal); @@ -128,7 +129,7 @@ void AmbientOcclusionEffect::render() { _occlusionProgram->setUniformValue(_leftBottomLocation, left, bottom); _occlusionProgram->setUniformValue(_rightTopLocation, right, top); _occlusionProgram->setUniformValue(_noiseScaleLocation, viewport[VIEWPORT_WIDTH_INDEX] / (float)ROTATION_WIDTH, - primaryFBO->height() / (float)ROTATION_HEIGHT); + framebufferSize.height() / (float)ROTATION_HEIGHT); _occlusionProgram->setUniformValue(_texCoordOffsetLocation, sMin, 0.0f); _occlusionProgram->setUniformValue(_texCoordScaleLocation, sWidth, 1.0f); @@ -136,22 +137,24 @@ void AmbientOcclusionEffect::render() { _occlusionProgram->release(); - freeFBO->release(); - + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); // now render secondary to primary with 4x4 blur - DependencyManager::get()->getPrimaryFramebufferObject()->bind(); - + auto primaryFramebuffer = DependencyManager::get()->getPrimaryFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFramebuffer)); + glEnable(GL_BLEND); glBlendFuncSeparate(GL_ZERO, GL_SRC_COLOR, GL_ZERO, GL_ONE); - glBindTexture(GL_TEXTURE_2D, freeFBO->texture()); + auto freeFramebufferTexture = freeFramebuffer->getRenderBuffer(0); + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(freeFramebufferTexture)); _blurProgram->bind(); - _blurProgram->setUniformValue(_blurScaleLocation, 1.0f / primaryFBO->width(), 1.0f / primaryFBO->height()); + _blurProgram->setUniformValue(_blurScaleLocation, 1.0f / framebufferSize.width(), 1.0f / framebufferSize.height()); renderFullscreenQuad(sMin, sMin + sWidth); diff --git a/libraries/render-utils/src/AnimationHandle.cpp b/libraries/render-utils/src/AnimationHandle.cpp index 1cb3d4654f..55995b87d8 100644 --- a/libraries/render-utils/src/AnimationHandle.cpp +++ b/libraries/render-utils/src/AnimationHandle.cpp @@ -47,10 +47,11 @@ void AnimationHandle::setPriority(float priority) { } void AnimationHandle::setStartAutomatically(bool startAutomatically) { - _animationLoop.setStartAutomatically(startAutomatically); - if (getStartAutomatically() && !isRunning()) { + if (startAutomatically && !isRunning()) { + // Start before setting _animationLoop value so that code in setRunning() is executed start(); } + _animationLoop.setStartAutomatically(startAutomatically); } void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { @@ -59,13 +60,10 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { } void AnimationHandle::setRunning(bool running) { - if (isRunning() == running) { + if (running && isRunning()) { // if we're already running, this is the same as a restart - if (running) { - // move back to the beginning - setFrameIndex(getFirstFrame()); - return; - } + setFrameIndex(getFirstFrame()); + return; } _animationLoop.setRunning(running); if (isRunning()) { diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index ecb1f503c7..cbf7c31d1f 100755 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -66,7 +66,7 @@ vec3 evalAmbienGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec3 color = diffuse.rgb * getLightColor(light) * 0.5; + vec3 color = diffuse.rgb * getLightColor(light) * getLightAmbientIntensity(light); vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); @@ -83,7 +83,7 @@ vec3 evalAmbienSphereGlobalColor(float shadowAttenuation, vec3 position, vec3 no vec3 fragEyeDir = normalize(fragEyeVector.xyz); vec3 ambientNormal = fragNormal.xyz; - vec3 color = diffuse.rgb * 0.5 * evalSphericalLight(ambientSphere, ambientNormal).xyz; + vec3 color = diffuse.rgb * evalSphericalLight(ambientSphere, ambientNormal).xyz * getLightAmbientIntensity(light); vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); @@ -112,7 +112,7 @@ vec3 evalLightmappedColor(float shadowAttenuation, vec3 normal, vec3 diffuse, ve vec3 diffuseLight = lightAttenuation * lightmap; // ambient is a tiny percentage of the lightmap and only when in the shadow - vec3 ambientLight = (1 - lightAttenuation) * 0.5 * lightmap; + vec3 ambientLight = (1 - lightAttenuation) * lightmap * getLightAmbientIntensity(light); return diffuse * (ambientLight + diffuseLight); } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index a4f45802a4..bddb1815bf 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -12,7 +12,6 @@ // include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL #include -#include #include #include @@ -183,15 +182,18 @@ void DeferredLightingEffect::render() { auto textureCache = DependencyManager::get(); - QOpenGLFramebufferObject* primaryFBO = textureCache->getPrimaryFramebufferObject(); - primaryFBO->release(); + glBindFramebuffer(GL_FRAMEBUFFER, 0 ); + + QSize framebufferSize = textureCache->getFrameBufferSize(); - QOpenGLFramebufferObject* freeFBO = DependencyManager::get()->getFreeFramebufferObject(); - freeFBO->bind(); + auto freeFBO = DependencyManager::get()->getFreeFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(freeFBO)); + glClear(GL_COLOR_BUFFER_BIT); // glEnable(GL_FRAMEBUFFER_SRGB); - glBindTexture(GL_TEXTURE_2D, primaryFBO->texture()); + // glBindTexture(GL_TEXTURE_2D, primaryFBO->texture()); + glBindTexture(GL_TEXTURE_2D, textureCache->getPrimaryColorTextureID()); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, textureCache->getPrimaryNormalTextureID()); @@ -209,11 +211,13 @@ void DeferredLightingEffect::render() { const int VIEWPORT_Y_INDEX = 1; const int VIEWPORT_WIDTH_INDEX = 2; const int VIEWPORT_HEIGHT_INDEX = 3; - float sMin = viewport[VIEWPORT_X_INDEX] / (float)primaryFBO->width(); - float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)primaryFBO->width(); - float tMin = viewport[VIEWPORT_Y_INDEX] / (float)primaryFBO->height(); - float tHeight = viewport[VIEWPORT_HEIGHT_INDEX] / (float)primaryFBO->height(); - + + float sMin = viewport[VIEWPORT_X_INDEX] / (float)framebufferSize.width(); + float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)framebufferSize.width(); + float tMin = viewport[VIEWPORT_Y_INDEX] / (float)framebufferSize.height(); + float tHeight = viewport[VIEWPORT_HEIGHT_INDEX] / (float)framebufferSize.height(); + + // Fetch the ViewMatrix; glm::mat4 invViewMat; _viewState->getViewTransform().getMatrix(invViewMat); @@ -245,7 +249,7 @@ void DeferredLightingEffect::render() { program->bind(); } program->setUniformValue(locations->shadowScale, - 1.0f / textureCache->getShadowFramebufferObject()->width()); + 1.0f / textureCache->getShadowFramebuffer()->getWidth()); } else { if (_ambientLightMode > -1) { @@ -428,7 +432,9 @@ void DeferredLightingEffect::render() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); - freeFBO->release(); + //freeFBO->release(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // glDisable(GL_FRAMEBUFFER_SRGB); glDisable(GL_CULL_FACE); @@ -437,9 +443,12 @@ void DeferredLightingEffect::render() { glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glColorMask(true, true, true, false); - primaryFBO->bind(); + auto primaryFBO = gpu::GLBackend::getFramebufferID(textureCache->getPrimaryFramebuffer()); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, primaryFBO); + + //primaryFBO->bind(); - glBindTexture(GL_TEXTURE_2D, freeFBO->texture()); + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(freeFBO->getRenderBuffer(0))); glEnable(GL_TEXTURE_2D); glPushMatrix(); @@ -545,11 +554,12 @@ void DeferredLightingEffect::setAmbientLightMode(int preset) { } } -void DeferredLightingEffect::setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity) { +void DeferredLightingEffect::setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity, float ambientIntensity) { auto light = _allocatedLights.front(); light->setDirection(direction); light->setColor(diffuse); light->setIntensity(intensity); + light->setAmbientIntensity(ambientIntensity); } void DeferredLightingEffect::setGlobalSkybox(const model::SkyboxPointer& skybox) { diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 6d0d131029..0d3d370fd4 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -73,7 +73,7 @@ public: // update global lighting void setAmbientLightMode(int preset); - void setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity); + void setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity, float ambientIntensity); void setGlobalAtmosphere(const model::AtmospherePointer& atmosphere) { _atmosphere = atmosphere; } void setGlobalSkybox(const model::SkyboxPointer& skybox); diff --git a/libraries/render-utils/src/FboCache.cpp b/libraries/render-utils/src/FboCache.cpp new file mode 100644 index 0000000000..2ca3d47bdf --- /dev/null +++ b/libraries/render-utils/src/FboCache.cpp @@ -0,0 +1,98 @@ +// +// OffscreenGlCanvas.cpp +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "FboCache.h" + +#include +#include +#include "ThreadHelpers.h" + +FboCache::FboCache() { + // Why do we even HAVE that lever? +} + +void FboCache::lockTexture(int texture) { + withLock(_lock, [&] { + Q_ASSERT(_fboMap.count(texture)); + if (!_fboLocks.count(texture)) { + Q_ASSERT(_readyFboQueue.front()->texture() == texture); + _readyFboQueue.pop_front(); + _fboLocks[texture] = 1; + } else { + _fboLocks[texture]++; + } + }); +} + +void FboCache::releaseTexture(int texture) { + withLock(_lock, [&] { + Q_ASSERT(_fboMap.count(texture)); + Q_ASSERT(_fboLocks.count(texture)); + int newLockCount = --_fboLocks[texture]; + if (!newLockCount) { + auto fbo = _fboMap[texture].data(); + if (fbo->size() != _size) { + // Move the old FBO to the destruction queue. + // We can't destroy the FBO here because we might + // not be on the right thread or have the context active + _destroyFboQueue.push_back(_fboMap[texture]); + _fboMap.remove(texture); + } else { + _readyFboQueue.push_back(fbo); + } + _fboLocks.remove(texture); + } + }); +} + +QOpenGLFramebufferObject* FboCache::getReadyFbo() { + QOpenGLFramebufferObject* result = nullptr; + withLock(_lock, [&] { + // Delete any FBOs queued for deletion + _destroyFboQueue.clear(); + + if (_readyFboQueue.empty()) { + qDebug() << "Building new offscreen FBO number " << _fboMap.size() + 1; + result = new QOpenGLFramebufferObject(_size, QOpenGLFramebufferObject::CombinedDepthStencil); + _fboMap[result->texture()] = QSharedPointer(result); + _readyFboQueue.push_back(result); + } else { + result = _readyFboQueue.front(); + } + }); + return result; +} + +void FboCache::setSize(const QSize& newSize) { + if (_size == newSize) { + return; + } + _size = newSize; + withLock(_lock, [&] { + // Clear out any fbos with the old id + _readyFboQueue.clear(); + + QSet outdatedFbos; + // FBOs that are locked will be removed as they are unlocked + foreach(int texture, _fboMap.keys()) { + if (!_fboLocks.count(texture)) { + outdatedFbos.insert(texture); + } + } + // Implicitly deletes the FBO via the shared pointer destruction mechanism + foreach(int texture, outdatedFbos) { + _fboMap.remove(texture); + } + }); +} + + diff --git a/libraries/render-utils/src/FboCache.h b/libraries/render-utils/src/FboCache.h new file mode 100644 index 0000000000..30278470fd --- /dev/null +++ b/libraries/render-utils/src/FboCache.h @@ -0,0 +1,51 @@ +// +// OffscreenGlCanvas.h +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// 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 +// +#pragma once +#ifndef hifi_FboCache_h +#define hifi_FboCache_h + +#include +#include +#include +#include +#include +#include + +class QOpenGLFramebufferObject; + +class FboCache : public QObject { +public: + FboCache(); + + // setSize() and getReadyFbo() must consitently be called from only a single + // thread. Additionally, it is the caller's responsibility to ensure that + // the appropriate OpenGL context is active when doing so. + + // Important.... textures are sharable resources, but FBOs ARE NOT. + void setSize(const QSize& newSize); + QOpenGLFramebufferObject* getReadyFbo(); + + // These operations are thread safe and require no OpenGL context. They manipulate the + // internal locks and pointers but execute no OpenGL opreations. + void lockTexture(int texture); + void releaseTexture(int texture); + +protected: + QMap> _fboMap; + QMap _fboLocks; + QQueue _readyFboQueue; + QQueue> _destroyFboQueue; + QMutex _lock; + QSize _size; + +}; + +#endif // hifi_FboCache_h diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 051cbb0e17..9d71ec5cc2 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1796,7 +1796,9 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer(), -1 }; + FBXJoint joint = { false, QVector(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), + glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(), + glm::mat4(), QString(""), glm::vec3(), glm::quat(), SHAPE_TYPE_NONE, false}; _geometry.joints.append(joint); _geometry.leftEyeJointIndex = -1; _geometry.rightEyeJointIndex = -1; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 37156a6c71..8ccbb65dbb 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -324,7 +324,7 @@ public: const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } -// + QVector getJointMappings(const AnimationPointer& animation); virtual void setLoadPriority(const QPointer& owner, float priority); diff --git a/libraries/render-utils/src/GlowEffect.cpp b/libraries/render-utils/src/GlowEffect.cpp index 2ba1d7df13..6addd84da6 100644 --- a/libraries/render-utils/src/GlowEffect.cpp +++ b/libraries/render-utils/src/GlowEffect.cpp @@ -24,6 +24,7 @@ #include "TextureCache.h" #include "RenderUtilsLogging.h" +#include "gpu/GLBackend.h" GlowEffect::GlowEffect() : _initialized(false), @@ -45,10 +46,10 @@ GlowEffect::~GlowEffect() { } } -QOpenGLFramebufferObject* GlowEffect::getFreeFramebufferObject() const { +gpu::FramebufferPointer GlowEffect::getFreeFramebuffer() const { return (_isOddFrame ? - DependencyManager::get()->getSecondaryFramebufferObject(): - DependencyManager::get()->getTertiaryFramebufferObject()); + DependencyManager::get()->getSecondaryFramebuffer(): + DependencyManager::get()->getTertiaryFramebuffer()); } static ProgramObject* createProgram(const QString& name) { @@ -105,7 +106,10 @@ int GlowEffect::getDeviceHeight() const { void GlowEffect::prepare() { - DependencyManager::get()->getPrimaryFramebufferObject()->bind(); + auto primaryFBO = DependencyManager::get()->getPrimaryFramebuffer(); + GLuint fbo = gpu::GLBackend::getFramebufferID(primaryFBO); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _isEmpty = true; @@ -124,25 +128,28 @@ void GlowEffect::end() { glBlendColor(0.0f, 0.0f, 0.0f, _intensity = _intensityStack.pop()); } -static void maybeBind(QOpenGLFramebufferObject* fbo) { +static void maybeBind(const gpu::FramebufferPointer& fbo) { if (fbo) { - fbo->bind(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(fbo)); } } -static void maybeRelease(QOpenGLFramebufferObject* fbo) { +static void maybeRelease(const gpu::FramebufferPointer& fbo) { if (fbo) { - fbo->release(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); } } -QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { +gpu::FramebufferPointer GlowEffect::render(bool toTexture) { PerformanceTimer perfTimer("glowEffect"); auto textureCache = DependencyManager::get(); - QOpenGLFramebufferObject* primaryFBO = textureCache->getPrimaryFramebufferObject(); - primaryFBO->release(); - glBindTexture(GL_TEXTURE_2D, primaryFBO->texture()); + + auto primaryFBO = gpu::GLBackend::getFramebufferID(textureCache->getPrimaryFramebuffer()); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glBindTexture(GL_TEXTURE_2D, textureCache->getPrimaryColorTextureID()); + auto framebufferSize = textureCache->getFrameBufferSize(); glPushMatrix(); glLoadIdentity(); @@ -155,12 +162,14 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); - QOpenGLFramebufferObject* destFBO = toTexture ? - textureCache->getSecondaryFramebufferObject() : NULL; + gpu::FramebufferPointer destFBO = toTexture ? + textureCache->getSecondaryFramebuffer() : nullptr; if (!_enabled || _isEmpty) { // copy the primary to the screen if (destFBO && QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) { - QOpenGLFramebufferObject::blitFramebuffer(destFBO, primaryFBO); + glBindFramebuffer(GL_READ_FRAMEBUFFER, primaryFBO); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(destFBO)); + glBlitFramebuffer(0, 0, framebufferSize.width(), framebufferSize.height(), 0, 0, framebufferSize.width(), framebufferSize.height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); } else { maybeBind(destFBO); if (!destFBO) { @@ -175,35 +184,35 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { } } else { // diffuse into the secondary/tertiary (alternating between frames) - QOpenGLFramebufferObject* oldDiffusedFBO = - textureCache->getSecondaryFramebufferObject(); - QOpenGLFramebufferObject* newDiffusedFBO = - textureCache->getTertiaryFramebufferObject(); + auto oldDiffusedFBO = + textureCache->getSecondaryFramebuffer(); + auto newDiffusedFBO = + textureCache->getTertiaryFramebuffer(); if (_isOddFrame) { qSwap(oldDiffusedFBO, newDiffusedFBO); } - newDiffusedFBO->bind(); + glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(newDiffusedFBO)); if (_isFirstFrame) { glClear(GL_COLOR_BUFFER_BIT); } else { glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, oldDiffusedFBO->texture()); + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(oldDiffusedFBO->getRenderBuffer(0))); _diffuseProgram->bind(); - QSize size = primaryFBO->size(); - _diffuseProgram->setUniformValue(_diffusionScaleLocation, 1.0f / size.width(), 1.0f / size.height()); + + _diffuseProgram->setUniformValue(_diffusionScaleLocation, 1.0f / framebufferSize.width(), 1.0f / framebufferSize.height()); renderFullscreenQuad(); _diffuseProgram->release(); } - newDiffusedFBO->release(); - + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // add diffused texture to the primary - glBindTexture(GL_TEXTURE_2D, newDiffusedFBO->texture()); + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(newDiffusedFBO->getRenderBuffer(0))); if (toTexture) { destFBO = oldDiffusedFBO; diff --git a/libraries/render-utils/src/GlowEffect.h b/libraries/render-utils/src/GlowEffect.h index 895cd4bbce..6b7bad7689 100644 --- a/libraries/render-utils/src/GlowEffect.h +++ b/libraries/render-utils/src/GlowEffect.h @@ -13,6 +13,7 @@ #define hifi_GlowEffect_h #include +#include #include #include @@ -20,8 +21,6 @@ #include -class QOpenGLFramebufferObject; - class ProgramObject; /// A generic full screen glow effect. @@ -33,7 +32,7 @@ public: /// Returns a pointer to the framebuffer object that the glow effect is *not* using for persistent state /// (either the secondary or the tertiary). - QOpenGLFramebufferObject* getFreeFramebufferObject() const; + gpu::FramebufferPointer getFreeFramebuffer() const; void init(QGLWidget* widget, bool enabled); @@ -53,7 +52,7 @@ public: /// Renders the glow effect. To be called after rendering the scene. /// \param toTexture whether to render to a texture, rather than to the frame buffer /// \return the framebuffer object to which we rendered, or NULL if to the frame buffer - QOpenGLFramebufferObject* render(bool toTexture = false); + gpu::FramebufferPointer render(bool toTexture = false); public slots: void toggleGlowEffect(bool enabled); diff --git a/libraries/render-utils/src/JointState.cpp b/libraries/render-utils/src/JointState.cpp index cf98d69baa..a82a57f0ed 100644 --- a/libraries/render-utils/src/JointState.cpp +++ b/libraries/render-utils/src/JointState.cpp @@ -256,7 +256,7 @@ void JointState::setVisibleRotationInConstrainedFrame(const glm::quat& targetRot _visibleRotation = parentRotation * _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation; } -const bool JointState::rotationIsDefault(const glm::quat& rotation, float tolerance) const { +bool JointState::rotationIsDefault(const glm::quat& rotation, float tolerance) const { glm::quat defaultRotation = _fbxJoint->rotation; return glm::abs(rotation.x - defaultRotation.x) < tolerance && glm::abs(rotation.y - defaultRotation.y) < tolerance && diff --git a/libraries/render-utils/src/JointState.h b/libraries/render-utils/src/JointState.h index b502083463..363aeecd01 100644 --- a/libraries/render-utils/src/JointState.h +++ b/libraries/render-utils/src/JointState.h @@ -88,7 +88,7 @@ public: const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; } const glm::quat& getVisibleRotationInConstrainedFrame() const { return _visibleRotationInConstrainedFrame; } - const bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const; + bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const; glm::quat getDefaultRotationInParentFrame() const; const glm::vec3& getDefaultTranslationInConstrainedFrame() const; diff --git a/libraries/render-utils/src/MatrixStack.h b/libraries/render-utils/src/MatrixStack.h index 505818fc4a..feeda44058 100644 --- a/libraries/render-utils/src/MatrixStack.h +++ b/libraries/render-utils/src/MatrixStack.h @@ -38,7 +38,7 @@ public: push(glm::mat4()); } - explicit MatrixStack(const MatrixStack & other) { + explicit MatrixStack(const MatrixStack& other) : std::stack() { *((std::stack*)this) = *((std::stack*)&other); } @@ -173,12 +173,12 @@ public: } template - static void withPush(MatrixStack & stack, Function f) { + static void withPush(MatrixStack& stack, Function f) { stack.withPush(f); } template - static void withPush(MatrixStack & stack1, MatrixStack & stack2, Function f) { + static void withPush(MatrixStack& stack1, MatrixStack& stack2, Function f) { stack1.withPush([&]{ stack2.withPush(f); }); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b98123803f..7528d1db4d 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -111,7 +111,7 @@ void Model::RenderPipelineLib::addRenderPipeline(Model::RenderKey key, slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), 3)); gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); - bool makeResult = gpu::Shader::makeProgram(*program, slotBindings); + gpu::Shader::makeProgram(*program, slotBindings); auto locations = std::shared_ptr(new Locations()); @@ -130,7 +130,7 @@ void Model::RenderPipelineLib::addRenderPipeline(Model::RenderKey key, } // Z test depends if transparent or not - state->setDepthTest(true, !key.isTranslucent(), gpu::State::LESS_EQUAL); + state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL); // Blend on transparent state->setBlendFunction(key.isTranslucent(), @@ -139,7 +139,7 @@ void Model::RenderPipelineLib::addRenderPipeline(Model::RenderKey key, // Good to go add the brand new pipeline auto pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - auto it = insert(value_type(key.getRaw(), RenderPipeline(pipeline, locations))); + insert(value_type(key.getRaw(), RenderPipeline(pipeline, locations))); // If not a shadow pass, create the mirror version from the same state, just change the FrontFace if (!key.isShadow()) { @@ -151,7 +151,7 @@ void Model::RenderPipelineLib::addRenderPipeline(Model::RenderKey key, // create a new RenderPipeline with the same shader side and the mirrorState auto mirrorPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, mirrorState)); - auto it = insert(value_type(mirrorKey.getRaw(), RenderPipeline(mirrorPipeline, locations))); + insert(value_type(mirrorKey.getRaw(), RenderPipeline(mirrorPipeline, locations))); } } @@ -244,13 +244,6 @@ void Model::initJointTransforms() { void Model::init() { if (_renderPipelineLib.empty()) { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), 0)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), 2)); - slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), 3)); - // Vertex shaders auto modelVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_vert))); auto modelNormalMapVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_normal_map_vert))); @@ -291,10 +284,24 @@ void Model::init() { RenderKey(RenderKey::HAS_TANGENTS | RenderKey::HAS_SPECULAR), modelNormalMapVertex, modelNormalSpecularMapPixel); + _renderPipelineLib.addRenderPipeline( RenderKey(RenderKey::IS_TRANSLUCENT), modelVertex, modelTranslucentPixel); + _renderPipelineLib.addRenderPipeline( + RenderKey(RenderKey::HAS_TANGENTS | RenderKey::IS_TRANSLUCENT), + modelNormalMapVertex, modelTranslucentPixel); + + _renderPipelineLib.addRenderPipeline( + RenderKey(RenderKey::HAS_SPECULAR | RenderKey::IS_TRANSLUCENT), + modelVertex, modelTranslucentPixel); + + _renderPipelineLib.addRenderPipeline( + RenderKey(RenderKey::HAS_TANGENTS | RenderKey::HAS_SPECULAR | RenderKey::IS_TRANSLUCENT), + modelNormalMapVertex, modelTranslucentPixel); + + _renderPipelineLib.addRenderPipeline( RenderKey(RenderKey::HAS_LIGHTMAP), modelLightmapVertex, modelLightmapPixel); @@ -310,6 +317,7 @@ void Model::init() { RenderKey(RenderKey::HAS_LIGHTMAP | RenderKey::HAS_TANGENTS | RenderKey::HAS_SPECULAR), modelLightmapNormalMapVertex, modelLightmapNormalSpecularMapPixel); + _renderPipelineLib.addRenderPipeline( RenderKey(RenderKey::IS_SKINNED), skinModelVertex, modelPixel); @@ -326,15 +334,29 @@ void Model::init() { RenderKey(RenderKey::IS_SKINNED | RenderKey::HAS_TANGENTS | RenderKey::HAS_SPECULAR), skinModelNormalMapVertex, modelNormalSpecularMapPixel); + _renderPipelineLib.addRenderPipeline( RenderKey(RenderKey::IS_SKINNED | RenderKey::IS_TRANSLUCENT), skinModelVertex, modelTranslucentPixel); + _renderPipelineLib.addRenderPipeline( + RenderKey(RenderKey::IS_SKINNED | RenderKey::HAS_TANGENTS | RenderKey::IS_TRANSLUCENT), + skinModelNormalMapVertex, modelTranslucentPixel); + + _renderPipelineLib.addRenderPipeline( + RenderKey(RenderKey::IS_SKINNED | RenderKey::HAS_SPECULAR | RenderKey::IS_TRANSLUCENT), + skinModelVertex, modelTranslucentPixel); + + _renderPipelineLib.addRenderPipeline( + RenderKey(RenderKey::IS_SKINNED | RenderKey::HAS_TANGENTS | RenderKey::HAS_SPECULAR | RenderKey::IS_TRANSLUCENT), + skinModelNormalMapVertex, modelTranslucentPixel); + _renderPipelineLib.addRenderPipeline( RenderKey(RenderKey::IS_DEPTH_ONLY | RenderKey::IS_SHADOW), modelShadowVertex, modelShadowPixel); + _renderPipelineLib.addRenderPipeline( RenderKey(RenderKey::IS_SKINNED | RenderKey::IS_DEPTH_ONLY | RenderKey::IS_SHADOW), skinModelShadowVertex, modelShadowPixel); @@ -404,12 +426,12 @@ bool Model::updateGeometry() { _dilatedTextures.clear(); _geometry = geometry; _meshGroupsKnown = false; - setJointStates(newJointStates); + initJointStates(newJointStates); needToRebuild = true; } else if (_jointStates.isEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { - setJointStates(createJointStates(fbxGeometry)); + initJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } } else if (!geometry->isLoaded()) { @@ -447,7 +469,7 @@ bool Model::updateGeometry() { } // virtual -void Model::setJointStates(QVector states) { +void Model::initJointStates(QVector states) { _jointStates = states; initJointTransforms(); @@ -551,6 +573,61 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g return intersectedSomething; } +bool Model::convexHullContains(glm::vec3 point) { + // if we aren't active, we can't compute that yet... + if (!isActive()) { + return false; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::vec3 position = _translation; + glm::mat4 rotation = glm::mat4_cast(_rotation); + glm::mat4 translation = glm::translate(position); + glm::mat4 modelToWorldMatrix = translation * rotation; + glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); + + Extents modelExtents = getMeshExtents(); // NOTE: unrotated + + glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; + glm::vec3 corner = -(dimensions * _registrationPoint); + AABox modelFrameBox(corner, dimensions); + + glm::vec3 modelFramePoint = glm::vec3(worldToModelMatrix * glm::vec4(point, 1.0f)); + + // we can use the AABox's contains() by mapping our point into the model frame + // and testing there. + if (modelFrameBox.contains(modelFramePoint)){ + if (!_calculatedMeshTrianglesValid) { + recalculateMeshBoxes(true); + } + + // If we are inside the models box, then consider the submeshes... + int subMeshIndex = 0; + foreach(const AABox& subMeshBox, _calculatedMeshBoxes) { + if (subMeshBox.contains(point)) { + bool insideMesh = true; + // To be inside the sub mesh, we need to be behind every triangles' planes + const QVector& meshTriangles = _calculatedMeshTriangles[subMeshIndex]; + foreach (const Triangle& triangle, meshTriangles) { + if (!isPointBehindTrianglesPlane(point, triangle.v0, triangle.v1, triangle.v2)) { + // it's not behind at least one so we bail + insideMesh = false; + break; + } + + } + if (insideMesh) { + // It's inside this mesh, return true. + return true; + } + } + subMeshIndex++; + } + } + // It wasn't in any mesh, return false. + return false; +} + // TODO: we seem to call this too often when things haven't actually changed... look into optimizing this void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid; @@ -673,8 +750,6 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { return false; } - // auto glowEffectIntensity = DependencyManager::get()->getIntensity(); - // Let's introduce a gpu::Batch to capture all the calls to the graphics api _renderBatch.clear(); gpu::Batch& batch = _renderBatch; @@ -703,35 +778,12 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { batch.setViewTransform(_transforms[0]); - // GLBATCH(glDisable)(GL_COLOR_MATERIAL); - - // taking care of by the state? - /* if (mode == RenderArgs::DIFFUSE_RENDER_MODE || mode == RenderArgs::NORMAL_RENDER_MODE) { - GLBATCH(glDisable)(GL_CULL_FACE); - } else { - GLBATCH(glEnable)(GL_CULL_FACE); - if (mode == RenderArgs::SHADOW_RENDER_MODE) { - GLBATCH(glCullFace)(GL_FRONT); - } - } - */ - - // render opaque meshes with alpha testing - -// GLBATCH(glDisable)(GL_BLEND); -// GLBATCH(glEnable)(GL_ALPHA_TEST); - - /* if (mode == RenderArgs::SHADOW_RENDER_MODE) { - GLBATCH(glAlphaFunc)(GL_EQUAL, 0.0f); - } - */ - /*DependencyManager::get()->setPrimaryDrawBuffers( mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE, mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::NORMAL_RENDER_MODE, mode == RenderArgs::DEFAULT_RENDER_MODE); */ - { + /*if (mode != RenderArgs::SHADOW_RENDER_MODE)*/ { GLenum buffers[3]; int bufferCount = 0; @@ -748,6 +800,7 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; } GLBATCH(glDrawBuffers)(bufferCount, buffers); + // batch.setFramebuffer(DependencyManager::get()->getPrimaryOpaqueFramebuffer()); } const float DEFAULT_ALPHA_THRESHOLD = 0.5f; @@ -790,12 +843,6 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args, true); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args, true); - // GLBATCH(glDisable)(GL_ALPHA_TEST); - /* GLBATCH(glEnable)(GL_BLEND); - GLBATCH(glDepthMask)(false); - GLBATCH(glDepthFunc)(GL_LEQUAL); - */ - //DependencyManager::get()->setPrimaryDrawBuffers(true); { GLenum buffers[1]; int bufferCount = 0; @@ -805,6 +852,8 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { // if (mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE) { if (mode != RenderArgs::SHADOW_RENDER_MODE) { + // batch.setFramebuffer(DependencyManager::get()->getPrimaryTransparentFramebuffer()); + const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args, true); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args, true); @@ -814,6 +863,8 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args, true); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args, true); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args, true); + + // batch.setFramebuffer(DependencyManager::get()->getPrimaryOpaqueFramebuffer()); } GLBATCH(glDepthMask)(true); @@ -1051,7 +1102,7 @@ int Model::getLastFreeJointIndex(int jointIndex) const { void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) { // don't recreate the geometry if it's the same URL - if (_url == url) { + if (_url == url && _geometry && _geometry->getURL() == url) { return; } _url = url; @@ -1758,13 +1809,11 @@ void Model::setupBatchTransform(gpu::Batch& batch, RenderArgs* args) { void Model::endScene(RenderMode mode, RenderArgs* args) { PROFILE_RANGE(__FUNCTION__); - // auto glowEffectIntensity = DependencyManager::get()->getIntensity(); - - #if defined(ANDROID) - #else - glPushMatrix(); - #endif +#if (GPU_TRANSFORM_PROFILE == GPU_LEGACY) + // with legacy transform profile, we still to protect that transform stack... + glPushMatrix(); +#endif RenderArgs::RenderSide renderSide = RenderArgs::MONO; if (args) { @@ -1792,34 +1841,15 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { _sceneRenderBatch.clear(); gpu::Batch& batch = _sceneRenderBatch; - // GLBATCH(glDisable)(GL_COLOR_MATERIAL); - - /* if (mode == RenderArgs::DIFFUSE_RENDER_MODE || mode == RenderArgs::NORMAL_RENDER_MODE) { - GLBATCH(glDisable)(GL_CULL_FACE); - } else { - GLBATCH(glEnable)(GL_CULL_FACE); - if (mode == RenderArgs::SHADOW_RENDER_MODE) { - GLBATCH(glCullFace)(GL_FRONT); - } - }*/ - - // render opaque meshes with alpha testing - - // GLBATCH(glDisable)(GL_BLEND); - // GLBATCH(glEnable)(GL_ALPHA_TEST); - - /* if (mode == RenderArgs::SHADOW_RENDER_MODE) { - GLBATCH(glAlphaFunc)(GL_EQUAL, 0.0f); - } -*/ - /*DependencyManager::get()->setPrimaryDrawBuffers( mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE, mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::NORMAL_RENDER_MODE, mode == RenderArgs::DEFAULT_RENDER_MODE); */ - { + + /* if (mode != RenderArgs::SHADOW_RENDER_MODE) */{ GLenum buffers[3]; + int bufferCount = 0; // if (mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE) { if (mode != RenderArgs::SHADOW_RENDER_MODE) { @@ -1834,6 +1864,8 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; } GLBATCH(glDrawBuffers)(bufferCount, buffers); + + // batch.setFramebuffer(DependencyManager::get()->getPrimaryOpaqueFramebuffer()); } const float DEFAULT_ALPHA_THRESHOLD = 0.5f; @@ -1856,7 +1888,6 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { opaqueMeshPartsRendered += renderMeshesForModelsInScene(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args); // render translucent meshes afterwards - //DependencyManager::get()->setPrimaryDrawBuffers(false, true, true); { GLenum buffers[2]; int bufferCount = 0; @@ -1876,21 +1907,19 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args); translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args); - // GLBATCH(glDisable)(GL_ALPHA_TEST); - /* GLBATCH(glEnable)(GL_BLEND); - GLBATCH(glDepthMask)(false); - GLBATCH(glDepthFunc)(GL_LEQUAL); - */ - //DependencyManager::get()->setPrimaryDrawBuffers(true); + { GLenum buffers[1]; int bufferCount = 0; buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; GLBATCH(glDrawBuffers)(bufferCount, buffers); + // batch.setFramebuffer(DependencyManager::get()->getPrimaryTransparentFramebuffer()); } // if (mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE) { if (mode != RenderArgs::SHADOW_RENDER_MODE) { + // batch.setFramebuffer(DependencyManager::get()->getPrimaryTransparentFramebuffer()); + const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args); translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args); @@ -1900,6 +1929,8 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args); translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args); translucentParts += renderMeshesForModelsInScene(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args); + + // batch.setFramebuffer(DependencyManager::get()->getPrimaryOpaqueFramebuffer()); } GLBATCH(glDepthMask)(true); @@ -1938,10 +1969,10 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { // Back to no program GLBATCH(glUseProgram)(0); - if (args) { - args->_translucentMeshPartsRendered = translucentParts; - args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered; - } + if (args) { + args->_translucentMeshPartsRendered = translucentParts; + args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered; + } } @@ -1952,10 +1983,10 @@ void Model::endScene(RenderMode mode, RenderArgs* args) { } - #if defined(ANDROID) - #else - glPopMatrix(); - #endif +#if (GPU_TRANSFORM_PROFILE == GPU_LEGACY) + // with legacy transform profile, we still to protect that transform stack... + glPopMatrix(); +#endif // restore all the default material settings _viewState->setupWorldLight(); @@ -1971,14 +2002,14 @@ bool Model::renderInScene(float alpha, RenderArgs* args) { return false; } - if (args->_renderMode == RenderArgs::DEBUG_RENDER_MODE && _renderCollisionHull == false) { + if (args->_debugFlags == RenderArgs::RENDER_DEBUG_HULLS && _renderCollisionHull == false) { // turning collision hull rendering on _renderCollisionHull = true; _nextGeometry = _collisionGeometry; _saveNonCollisionGeometry = _geometry; updateGeometry(); simulate(0.0, true); - } else if (args->_renderMode != RenderArgs::DEBUG_RENDER_MODE && _renderCollisionHull == true) { + } else if (args->_debugFlags != RenderArgs::RENDER_DEBUG_HULLS && _renderCollisionHull == true) { // turning collision hull rendering off _renderCollisionHull = false; _nextGeometry = _saveNonCollisionGeometry; @@ -1993,55 +2024,7 @@ bool Model::renderInScene(float alpha, RenderArgs* args) { } void Model::segregateMeshGroups() { - _meshesTranslucentTangents.clear(); - _meshesTranslucent.clear(); - _meshesTranslucentTangentsSpecular.clear(); - _meshesTranslucentSpecular.clear(); - - _meshesTranslucentTangentsSkinned.clear(); - _meshesTranslucentSkinned.clear(); - _meshesTranslucentTangentsSpecularSkinned.clear(); - _meshesTranslucentSpecularSkinned.clear(); - - _meshesOpaqueTangents.clear(); - _meshesOpaque.clear(); - _meshesOpaqueTangentsSpecular.clear(); - _meshesOpaqueSpecular.clear(); - - _meshesOpaqueTangentsSkinned.clear(); - _meshesOpaqueSkinned.clear(); - _meshesOpaqueTangentsSpecularSkinned.clear(); - _meshesOpaqueSpecularSkinned.clear(); - - _meshesOpaqueLightmapTangents.clear(); - _meshesOpaqueLightmap.clear(); - _meshesOpaqueLightmapTangentsSpecular.clear(); - _meshesOpaqueLightmapSpecular.clear(); - - _unsortedMeshesTranslucentTangents.clear(); - _unsortedMeshesTranslucent.clear(); - _unsortedMeshesTranslucentTangentsSpecular.clear(); - _unsortedMeshesTranslucentSpecular.clear(); - - _unsortedMeshesTranslucentTangentsSkinned.clear(); - _unsortedMeshesTranslucentSkinned.clear(); - _unsortedMeshesTranslucentTangentsSpecularSkinned.clear(); - _unsortedMeshesTranslucentSpecularSkinned.clear(); - - _unsortedMeshesOpaqueTangents.clear(); - _unsortedMeshesOpaque.clear(); - _unsortedMeshesOpaqueTangentsSpecular.clear(); - _unsortedMeshesOpaqueSpecular.clear(); - - _unsortedMeshesOpaqueTangentsSkinned.clear(); - _unsortedMeshesOpaqueSkinned.clear(); - _unsortedMeshesOpaqueTangentsSpecularSkinned.clear(); - _unsortedMeshesOpaqueSpecularSkinned.clear(); - - _unsortedMeshesOpaqueLightmapTangents.clear(); - _unsortedMeshesOpaqueLightmap.clear(); - _unsortedMeshesOpaqueLightmapTangentsSpecular.clear(); - _unsortedMeshesOpaqueLightmapSpecular.clear(); + _renderBuckets.clear(); const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); @@ -2075,201 +2058,19 @@ void Model::segregateMeshGroups() { qCDebug(renderutils) << "materialID:" << materialID << "parts:" << mesh.parts.size(); } - if (!hasLightmap) { - if (translucentMesh && !hasTangents && !hasSpecular && !isSkinned) { + RenderKey key(translucentMesh, hasLightmap, hasTangents, hasSpecular, isSkinned); - _unsortedMeshesTranslucent.insertMulti(materialID, i); - - } else if (translucentMesh && hasTangents && !hasSpecular && !isSkinned) { - - _unsortedMeshesTranslucentTangents.insertMulti(materialID, i); - - } else if (translucentMesh && hasTangents && hasSpecular && !isSkinned) { - - _unsortedMeshesTranslucentTangentsSpecular.insertMulti(materialID, i); - - } else if (translucentMesh && !hasTangents && hasSpecular && !isSkinned) { - - _unsortedMeshesTranslucentSpecular.insertMulti(materialID, i); - - } else if (translucentMesh && hasTangents && !hasSpecular && isSkinned) { - - _unsortedMeshesTranslucentTangentsSkinned.insertMulti(materialID, i); - - } else if (translucentMesh && !hasTangents && !hasSpecular && isSkinned) { - - _unsortedMeshesTranslucentSkinned.insertMulti(materialID, i); - - } else if (translucentMesh && hasTangents && hasSpecular && isSkinned) { - - _unsortedMeshesTranslucentTangentsSpecularSkinned.insertMulti(materialID, i); - - } else if (translucentMesh && !hasTangents && hasSpecular && isSkinned) { - - _unsortedMeshesTranslucentSpecularSkinned.insertMulti(materialID, i); - - } else if (!translucentMesh && !hasTangents && !hasSpecular && !isSkinned) { - - _unsortedMeshesOpaque.insertMulti(materialID, i); - - } else if (!translucentMesh && hasTangents && !hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueTangents.insertMulti(materialID, i); - - } else if (!translucentMesh && hasTangents && hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueTangentsSpecular.insertMulti(materialID, i); - - } else if (!translucentMesh && !hasTangents && hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueSpecular.insertMulti(materialID, i); - - } else if (!translucentMesh && hasTangents && !hasSpecular && isSkinned) { - - _unsortedMeshesOpaqueTangentsSkinned.insertMulti(materialID, i); - - } else if (!translucentMesh && !hasTangents && !hasSpecular && isSkinned) { - - _unsortedMeshesOpaqueSkinned.insertMulti(materialID, i); - - } else if (!translucentMesh && hasTangents && hasSpecular && isSkinned) { - - _unsortedMeshesOpaqueTangentsSpecularSkinned.insertMulti(materialID, i); - - } else if (!translucentMesh && !hasTangents && hasSpecular && isSkinned) { - - _unsortedMeshesOpaqueSpecularSkinned.insertMulti(materialID, i); - } else { - qCDebug(renderutils) << "unexpected!!! this mesh didn't fall into any or our groups???"; - } - } else { - if (!translucentMesh && !hasTangents && !hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueLightmap.insertMulti(materialID, i); - - } else if (!translucentMesh && hasTangents && !hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueLightmapTangents.insertMulti(materialID, i); - - } else if (!translucentMesh && hasTangents && hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueLightmapTangentsSpecular.insertMulti(materialID, i); - - } else if (!translucentMesh && !hasTangents && hasSpecular && !isSkinned) { - - _unsortedMeshesOpaqueLightmapSpecular.insertMulti(materialID, i); - - } else { - qCDebug(renderutils) << "unexpected!!! this mesh didn't fall into any or our groups???"; - } - } + // reuse or create the bucket corresponding to that key and insert the mesh as unsorted + _renderBuckets[key.getRaw()]._unsortedMeshes.insertMulti(materialID, i); } - foreach(int i, _unsortedMeshesTranslucent) { - _meshesTranslucent.append(i); + for(auto& b : _renderBuckets) { + foreach(auto i, b.second._unsortedMeshes) { + b.second._meshes.append(i); + b.second._unsortedMeshes.clear(); + } } - foreach(int i, _unsortedMeshesTranslucentTangents) { - _meshesTranslucentTangents.append(i); - } - - foreach(int i, _unsortedMeshesTranslucentTangentsSpecular) { - _meshesTranslucentTangentsSpecular.append(i); - } - - foreach(int i, _unsortedMeshesTranslucentSpecular) { - _meshesTranslucentSpecular.append(i); - } - - foreach(int i, _unsortedMeshesTranslucentSkinned) { - _meshesTranslucentSkinned.append(i); - } - - foreach(int i, _unsortedMeshesTranslucentTangentsSkinned) { - _meshesTranslucentTangentsSkinned.append(i); - } - - foreach(int i, _unsortedMeshesTranslucentTangentsSpecularSkinned) { - _meshesTranslucentTangentsSpecularSkinned.append(i); - } - - foreach(int i, _unsortedMeshesTranslucentSpecularSkinned) { - _meshesTranslucentSpecularSkinned.append(i); - } - - foreach(int i, _unsortedMeshesOpaque) { - _meshesOpaque.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueTangents) { - _meshesOpaqueTangents.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueTangentsSpecular) { - _meshesOpaqueTangentsSpecular.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueSpecular) { - _meshesOpaqueSpecular.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueSkinned) { - _meshesOpaqueSkinned.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueTangentsSkinned) { - _meshesOpaqueTangentsSkinned.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueTangentsSpecularSkinned) { - _meshesOpaqueTangentsSpecularSkinned.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueSpecularSkinned) { - _meshesOpaqueSpecularSkinned.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueLightmap) { - _meshesOpaqueLightmap.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueLightmapTangents) { - _meshesOpaqueLightmapTangents.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueLightmapTangentsSpecular) { - _meshesOpaqueLightmapTangentsSpecular.append(i); - } - - foreach(int i, _unsortedMeshesOpaqueLightmapSpecular) { - _meshesOpaqueLightmapSpecular.append(i); - } - - _unsortedMeshesTranslucentTangents.clear(); - _unsortedMeshesTranslucent.clear(); - _unsortedMeshesTranslucentTangentsSpecular.clear(); - _unsortedMeshesTranslucentSpecular.clear(); - - _unsortedMeshesTranslucentTangentsSkinned.clear(); - _unsortedMeshesTranslucentSkinned.clear(); - _unsortedMeshesTranslucentTangentsSpecularSkinned.clear(); - _unsortedMeshesTranslucentSpecularSkinned.clear(); - - _unsortedMeshesOpaqueTangents.clear(); - _unsortedMeshesOpaque.clear(); - _unsortedMeshesOpaqueTangentsSpecular.clear(); - _unsortedMeshesOpaqueSpecular.clear(); - - _unsortedMeshesOpaqueTangentsSkinned.clear(); - _unsortedMeshesOpaqueSkinned.clear(); - _unsortedMeshesOpaqueTangentsSpecularSkinned.clear(); - _unsortedMeshesOpaqueSpecularSkinned.clear(); - - _unsortedMeshesOpaqueLightmapTangents.clear(); - _unsortedMeshesOpaqueLightmap.clear(); - _unsortedMeshesOpaqueLightmapTangentsSpecular.clear(); - _unsortedMeshesOpaqueLightmapSpecular.clear(); - _meshGroupsKnown = true; } @@ -2278,52 +2079,14 @@ QVector* Model::pickMeshList(bool translucent, float alphaThreshold, bool h // depending on which parameters we were called with, pick the correct mesh group to render QVector* whichList = NULL; - if (translucent && !hasTangents && !hasSpecular && !isSkinned) { - whichList = &_meshesTranslucent; - } else if (translucent && hasTangents && !hasSpecular && !isSkinned) { - whichList = &_meshesTranslucentTangents; - } else if (translucent && hasTangents && hasSpecular && !isSkinned) { - whichList = &_meshesTranslucentTangentsSpecular; - } else if (translucent && !hasTangents && hasSpecular && !isSkinned) { - whichList = &_meshesTranslucentSpecular; - } else if (translucent && hasTangents && !hasSpecular && isSkinned) { - whichList = &_meshesTranslucentTangentsSkinned; - } else if (translucent && !hasTangents && !hasSpecular && isSkinned) { - whichList = &_meshesTranslucentSkinned; - } else if (translucent && hasTangents && hasSpecular && isSkinned) { - whichList = &_meshesTranslucentTangentsSpecularSkinned; - } else if (translucent && !hasTangents && hasSpecular && isSkinned) { - whichList = &_meshesTranslucentSpecularSkinned; - } else if (!translucent && !hasLightmap && !hasTangents && !hasSpecular && !isSkinned) { - whichList = &_meshesOpaque; - } else if (!translucent && !hasLightmap && hasTangents && !hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueTangents; - } else if (!translucent && !hasLightmap && hasTangents && hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueTangentsSpecular; - } else if (!translucent && !hasLightmap && !hasTangents && hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueSpecular; - } else if (!translucent && !hasLightmap && hasTangents && !hasSpecular && isSkinned) { - whichList = &_meshesOpaqueTangentsSkinned; - } else if (!translucent && !hasLightmap && !hasTangents && !hasSpecular && isSkinned) { - whichList = &_meshesOpaqueSkinned; - } else if (!translucent && !hasLightmap && hasTangents && hasSpecular && isSkinned) { - whichList = &_meshesOpaqueTangentsSpecularSkinned; - } else if (!translucent && !hasLightmap && !hasTangents && hasSpecular && isSkinned) { - whichList = &_meshesOpaqueSpecularSkinned; + RenderKey key(translucent, hasLightmap, hasTangents, hasSpecular, isSkinned); - } else if (!translucent && hasLightmap && !hasTangents && !hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueLightmap; - } else if (!translucent && hasLightmap && hasTangents && !hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueLightmapTangents; - } else if (!translucent && hasLightmap && hasTangents && hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueLightmapTangentsSpecular; - } else if (!translucent && hasLightmap && !hasTangents && hasSpecular && !isSkinned) { - whichList = &_meshesOpaqueLightmapSpecular; - - } else { - qCDebug(renderutils) << "unexpected!!! this mesh didn't fall into any or our groups???"; + auto bucket = _renderBuckets.find(key.getRaw()); + if (bucket != _renderBuckets.end()) { + whichList = &(*bucket).second._meshes; } + return whichList; } @@ -2334,17 +2097,16 @@ void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, f RenderKey key(mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned); auto pipeline = _renderPipelineLib.find(key.getRaw()); if (pipeline == _renderPipelineLib.end()) { - qDebug() << "No good, couldn;t find a pipeline from the key ?" << key.getRaw(); + qDebug() << "No good, couldn't find a pipeline from the key ?" << key.getRaw(); + locations = 0; return; } gpu::ShaderPointer program = (*pipeline).second._pipeline->getProgram(); locations = (*pipeline).second._locations.get(); - //GLuint glprogram = gpu::GLBackend::getShaderID(program); - //GLBATCH(glUseProgram)(glprogram); - // dare! + // Setup the One pipeline batch.setPipeline((*pipeline).second._pipeline); if ((locations->alphaThreshold > -1) && (mode != RenderArgs::SHADOW_RENDER_MODE)) { @@ -2354,9 +2116,6 @@ void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, f if ((locations->glowIntensity > -1) && (mode != RenderArgs::SHADOW_RENDER_MODE)) { GLBATCH(glUniform1f)(locations->glowIntensity, DependencyManager::get()->getIntensity()); } - // if (!(translucent && alphaThreshold == 0.0f) && (mode != RenderArgs::SHADOW_RENDER_MODE)) { - // GLBATCH(glAlphaFunc)(GL_EQUAL, DependencyManager::get()->getIntensity()); - // } } int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, @@ -2366,7 +2125,7 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool int meshPartsRendered = 0; bool pickProgramsNeeded = true; - Locations* locations; + Locations* locations = nullptr; foreach(Model* model, _modelsInScene) { QVector* whichList = model->pickMeshList(translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned); @@ -2383,10 +2142,7 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool } } } - // if we selected a program, then unselect it - if (!pickProgramsNeeded) { - // GLBATCH(glUseProgram)(0); - } + return meshPartsRendered; } @@ -2397,26 +2153,23 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl PROFILE_RANGE(__FUNCTION__); int meshPartsRendered = 0; - QVector* whichList = pickMeshList(translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned); - + //Pick the mesh list with the requested render flags + QVector* whichList = pickMeshList(translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned); if (!whichList) { - qCDebug(renderutils) << "unexpected!!! we don't know which list of meshes to render..."; return 0; } QVector& list = *whichList; // If this list has nothing to render, then don't bother proceeding. This saves us on binding to programs - if (list.size() == 0) { + if (list.empty()) { return 0; } - Locations* locations; + Locations* locations = nullptr; pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations); meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, args, locations, forceRenderSomeMeshes); - // GLBATCH(glUseProgram)(0); - return meshPartsRendered; } @@ -2427,7 +2180,7 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod PROFILE_RANGE(__FUNCTION__); auto textureCache = DependencyManager::get(); - // auto glowEffect = DependencyManager::get(); + QString lastMaterialID; int meshPartsRendered = 0; updateVisibleJointStates(); @@ -2531,13 +2284,6 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod qCDebug(renderutils) << "NEW part.materialID:" << part.materialID; } -/* if (locations->glowIntensity >= 0) { - GLBATCH(glUniform1f)(locations->glowIntensity, glowEffect->getIntensity()); - } - if (!(translucent && alphaThreshold == 0.0f)) { - GLBATCH(glAlphaFunc)(GL_EQUAL, glowEffect->getIntensity()); - } -*/ if (locations->materialBufferUnit >= 0) { batch.setUniformBuffer(locations->materialBufferUnit, material->getSchemaBuffer()); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index fce5629b8d..dba8f5277f 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -213,6 +213,7 @@ public: bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, QString& extraInfo, bool pickAgainstTriangles = false); + bool convexHullContains(glm::vec3 point); protected: QSharedPointer _geometry; @@ -244,7 +245,7 @@ protected: // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); - virtual void setJointStates(QVector states); + virtual void initJointStates(QVector states); void setScaleInternal(const glm::vec3& scale); void scaleToFit(); @@ -350,58 +351,6 @@ private: bool _meshGroupsKnown; - QMap _unsortedMeshesTranslucent; - QMap _unsortedMeshesTranslucentTangents; - QMap _unsortedMeshesTranslucentTangentsSpecular; - QMap _unsortedMeshesTranslucentSpecular; - - QMap _unsortedMeshesTranslucentSkinned; - QMap _unsortedMeshesTranslucentTangentsSkinned; - QMap _unsortedMeshesTranslucentTangentsSpecularSkinned; - QMap _unsortedMeshesTranslucentSpecularSkinned; - - QMap _unsortedMeshesOpaque; - QMap _unsortedMeshesOpaqueTangents; - QMap _unsortedMeshesOpaqueTangentsSpecular; - QMap _unsortedMeshesOpaqueSpecular; - - QMap _unsortedMeshesOpaqueSkinned; - QMap _unsortedMeshesOpaqueTangentsSkinned; - QMap _unsortedMeshesOpaqueTangentsSpecularSkinned; - QMap _unsortedMeshesOpaqueSpecularSkinned; - - QMap _unsortedMeshesOpaqueLightmap; - QMap _unsortedMeshesOpaqueLightmapTangents; - QMap _unsortedMeshesOpaqueLightmapTangentsSpecular; - QMap _unsortedMeshesOpaqueLightmapSpecular; - - typedef std::unordered_map> MeshListMap; - MeshListMap _sortedMeshes; - - QVector _meshesTranslucent; - QVector _meshesTranslucentTangents; - QVector _meshesTranslucentTangentsSpecular; - QVector _meshesTranslucentSpecular; - - QVector _meshesTranslucentSkinned; - QVector _meshesTranslucentTangentsSkinned; - QVector _meshesTranslucentTangentsSpecularSkinned; - QVector _meshesTranslucentSpecularSkinned; - - QVector _meshesOpaque; - QVector _meshesOpaqueTangents; - QVector _meshesOpaqueTangentsSpecular; - QVector _meshesOpaqueSpecular; - - QVector _meshesOpaqueSkinned; - QVector _meshesOpaqueTangentsSkinned; - QVector _meshesOpaqueTangentsSpecularSkinned; - QVector _meshesOpaqueSpecularSkinned; - - QVector _meshesOpaqueLightmap; - QVector _meshesOpaqueLightmapTangents; - QVector _meshesOpaqueLightmapTangentsSpecular; - QVector _meshesOpaqueLightmapSpecular; // debug rendering support void renderDebugMeshBoxes(); @@ -490,6 +439,17 @@ private: int getRaw() { return *reinterpret_cast(this); } + + RenderKey( + bool translucent, bool hasLightmap, + bool hasTangents, bool hasSpecular, bool isSkinned) : + RenderKey( (translucent ? IS_TRANSLUCENT : 0) + | (hasLightmap ? HAS_LIGHTMAP : 0) + | (hasTangents ? HAS_TANGENTS : 0) + | (hasSpecular ? HAS_SPECULAR : 0) + | (isSkinned ? IS_SKINNED : 0) + ) {} + RenderKey(RenderArgs::RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned) : @@ -527,6 +487,19 @@ private: }; static RenderPipelineLib _renderPipelineLib; + + class RenderBucket { + public: + QVector _meshes; + QMap _unsortedMeshes; + }; + typedef std::unordered_map BaseRenderBucketMap; + class RenderBucketMap : public BaseRenderBucketMap { + public: + typedef RenderKey Key; + }; + RenderBucketMap _renderBuckets; + bool _renderCollisionHull; }; diff --git a/libraries/render-utils/src/OffscreenGlCanvas.cpp b/libraries/render-utils/src/OffscreenGlCanvas.cpp new file mode 100644 index 0000000000..0e5d4928d8 --- /dev/null +++ b/libraries/render-utils/src/OffscreenGlCanvas.cpp @@ -0,0 +1,45 @@ +// +// OffscreenGlCanvas.cpp +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "OffscreenGlCanvas.h" + +OffscreenGlCanvas::OffscreenGlCanvas() { +} + +void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) { + if (nullptr != sharedContext) { + sharedContext->doneCurrent(); + _context.setFormat(sharedContext->format()); + _context.setShareContext(sharedContext); + } else { + QSurfaceFormat format; + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setMajorVersion(4); + format.setMinorVersion(1); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + _context.setFormat(format); + } + _context.create(); + + _offscreenSurface.setFormat(_context.format()); + _offscreenSurface.create(); +} + +bool OffscreenGlCanvas::makeCurrent() { + return _context.makeCurrent(&_offscreenSurface); +} + +void OffscreenGlCanvas::doneCurrent() { + _context.doneCurrent(); +} + diff --git a/libraries/render-utils/src/OffscreenGlCanvas.h b/libraries/render-utils/src/OffscreenGlCanvas.h new file mode 100644 index 0000000000..5a64a4cf10 --- /dev/null +++ b/libraries/render-utils/src/OffscreenGlCanvas.h @@ -0,0 +1,31 @@ +// +// OffscreenGlCanvas.h +// interface/src/renderer +// +// Created by Bradley Austin Davis on 2014/04/09. +// 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 +// +#pragma once +#ifndef hifi_OffscreenGlCanvas_h +#define hifi_OffscreenGlCanvas_h + +#include +#include + +class OffscreenGlCanvas : public QObject { +public: + OffscreenGlCanvas(); + void create(QOpenGLContext* sharedContext = nullptr); + bool makeCurrent(); + void doneCurrent(); + +protected: + QOpenGLContext _context; + QOffscreenSurface _offscreenSurface; + +}; + +#endif // hifi_OffscreenGlCanvas_h diff --git a/libraries/render-utils/src/RenderUtil.h b/libraries/render-utils/src/RenderUtil.h index b2f244733a..8c1b1e12e7 100644 --- a/libraries/render-utils/src/RenderUtil.h +++ b/libraries/render-utils/src/RenderUtil.h @@ -15,4 +15,54 @@ /// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, tMin) to (sMax, tMax). void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f, float tMin = 0.0f, float tMax = 1.0f); +template +void withMatrixPush(F f) { + glMatrixMode(matrix); + glPushMatrix(); + f(); + glPopMatrix(); +} + +template +void withProjectionPush(F f) { + withMatrixPush(f); +} + +template +void withProjectionIdentity(F f) { + withProjectionPush([&] { + glLoadIdentity(); + f(); + }); +} + +template +void withProjectionMatrix(GLfloat* matrix, F f) { + withProjectionPush([&] { + glLoadMatrixf(matrix); + f(); + }); +} + +template +void withModelviewPush(F f) { + withMatrixPush(f); +} + +template +void withModelviewIdentity(F f) { + withModelviewPush([&] { + glLoadIdentity(); + f(); + }); +} + +template +void withModelviewMatrix(GLfloat* matrix, F f) { + withModelviewPush([&] { + glLoadMatrixf(matrix); + f(); + }); +} + #endif // hifi_RenderUtil_h diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 9b2a458231..616ff13dce 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -28,17 +29,12 @@ #include "gpu/GLBackend.h" +#include + TextureCache::TextureCache() : _permutationNormalTexture(0), _whiteTexture(0), _blueTexture(0), - _primaryDepthTextureID(0), - _primaryNormalTextureID(0), - _primarySpecularTextureID(0), - _primaryFramebufferObject(NULL), - _secondaryFramebufferObject(NULL), - _tertiaryFramebufferObject(NULL), - _shadowFramebufferObject(NULL), _frameBufferSize(100, 100), _associatedWidget(NULL) { @@ -47,24 +43,6 @@ TextureCache::TextureCache() : } TextureCache::~TextureCache() { - - if (_primaryFramebufferObject) { - glDeleteTextures(1, &_primaryDepthTextureID); - glDeleteTextures(1, &_primaryNormalTextureID); - glDeleteTextures(1, &_primarySpecularTextureID); - } - - if (_primaryFramebufferObject) { - delete _primaryFramebufferObject; - } - - if (_secondaryFramebufferObject) { - delete _secondaryFramebufferObject; - } - - if (_tertiaryFramebufferObject) { - delete _tertiaryFramebufferObject; - } } void TextureCache::setFrameBufferSize(QSize frameBufferSize) { @@ -72,26 +50,15 @@ void TextureCache::setFrameBufferSize(QSize frameBufferSize) { if (_frameBufferSize != frameBufferSize) { _frameBufferSize = frameBufferSize; - if (_primaryFramebufferObject) { - delete _primaryFramebufferObject; - _primaryFramebufferObject = NULL; - glDeleteTextures(1, &_primaryDepthTextureID); - _primaryDepthTextureID = 0; - glDeleteTextures(1, &_primaryNormalTextureID); - _primaryNormalTextureID = 0; - glDeleteTextures(1, &_primarySpecularTextureID); - _primarySpecularTextureID = 0; - } + _primaryFramebuffer.reset(); + _primaryDepthTexture.reset(); + _primaryColorTexture.reset(); + _primaryNormalTexture.reset(); + _primarySpecularTexture.reset(); - if (_secondaryFramebufferObject) { - delete _secondaryFramebufferObject; - _secondaryFramebufferObject = NULL; - } + _secondaryFramebuffer.reset(); - if (_tertiaryFramebufferObject) { - delete _tertiaryFramebufferObject; - _tertiaryFramebufferObject = NULL; - } + _tertiaryFramebuffer.reset(); } } @@ -206,58 +173,78 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type return texture; } -QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() { +void TextureCache::createPrimaryFramebuffer() { + _primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); - if (!_primaryFramebufferObject) { - _primaryFramebufferObject = createFramebufferObject(); + auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + auto width = _frameBufferSize.width(); + auto height = _frameBufferSize.height(); - glGenTextures(1, &_primaryDepthTextureID); - glBindTexture(GL_TEXTURE_2D, _primaryDepthTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _frameBufferSize.width(), _frameBufferSize.height(), - 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - glGenTextures(1, &_primaryNormalTextureID); - glBindTexture(GL_TEXTURE_2D, _primaryNormalTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _frameBufferSize.width(), _frameBufferSize.height(), - 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); - - glGenTextures(1, &_primarySpecularTextureID); - glBindTexture(GL_TEXTURE_2D, _primarySpecularTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _frameBufferSize.width(), _frameBufferSize.height(), - 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); - - _primaryFramebufferObject->bind(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _primaryDepthTextureID, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, _primaryNormalTextureID, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, _primarySpecularTextureID, 0); - _primaryFramebufferObject->release(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _primaryColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + _primaryNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + _primarySpecularTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + + _primaryFramebuffer->setRenderBuffer(0, _primaryColorTexture); + _primaryFramebuffer->setRenderBuffer(1, _primaryNormalTexture); + _primaryFramebuffer->setRenderBuffer(2, _primarySpecularTexture); + + + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); + _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler)); + + _primaryFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); +} + +gpu::FramebufferPointer TextureCache::getPrimaryFramebuffer() { + if (!_primaryFramebuffer) { + createPrimaryFramebuffer(); } - return _primaryFramebufferObject; + return _primaryFramebuffer; +} + +gpu::TexturePointer TextureCache::getPrimaryDepthTexture() { + if (!_primaryDepthTexture) { + createPrimaryFramebuffer(); + } + return _primaryDepthTexture; +} + +gpu::TexturePointer TextureCache::getPrimaryColorTexture() { + if (!_primaryColorTexture) { + createPrimaryFramebuffer(); + } + return _primaryColorTexture; +} + +gpu::TexturePointer TextureCache::getPrimaryNormalTexture() { + if (!_primaryNormalTexture) { + createPrimaryFramebuffer(); + } + return _primaryNormalTexture; +} + +gpu::TexturePointer TextureCache::getPrimarySpecularTexture() { + if (!_primarySpecularTexture) { + createPrimaryFramebuffer(); + } + return _primarySpecularTexture; } GLuint TextureCache::getPrimaryDepthTextureID() { - // ensure that the primary framebuffer object is initialized before returning the depth texture id - getPrimaryFramebufferObject(); - return _primaryDepthTextureID; + return gpu::GLBackend::getTextureID(getPrimaryDepthTexture()); +} + +GLuint TextureCache::getPrimaryColorTextureID() { + return gpu::GLBackend::getTextureID(getPrimaryColorTexture()); } GLuint TextureCache::getPrimaryNormalTextureID() { - // ensure that the primary framebuffer object is initialized before returning the normal texture id - getPrimaryFramebufferObject(); - return _primaryNormalTextureID; + return gpu::GLBackend::getTextureID(getPrimaryNormalTexture()); } GLuint TextureCache::getPrimarySpecularTextureID() { - getPrimaryFramebufferObject(); - return _primarySpecularTextureID; + return gpu::GLBackend::getTextureID(getPrimarySpecularTexture()); } void TextureCache::setPrimaryDrawBuffers(bool color, bool normal, bool specular) { @@ -275,70 +262,50 @@ void TextureCache::setPrimaryDrawBuffers(bool color, bool normal, bool specular) glDrawBuffers(bufferCount, buffers); } -QOpenGLFramebufferObject* TextureCache::getSecondaryFramebufferObject() { - if (!_secondaryFramebufferObject) { - _secondaryFramebufferObject = createFramebufferObject(); +gpu::FramebufferPointer TextureCache::getSecondaryFramebuffer() { + if (!_secondaryFramebuffer) { + _secondaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, _frameBufferSize.width(), _frameBufferSize.height())); } - return _secondaryFramebufferObject; + return _secondaryFramebuffer; } -QOpenGLFramebufferObject* TextureCache::getTertiaryFramebufferObject() { - if (!_tertiaryFramebufferObject) { - _tertiaryFramebufferObject = createFramebufferObject(); +gpu::FramebufferPointer TextureCache::getTertiaryFramebuffer() { + if (!_tertiaryFramebuffer) { + _tertiaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, _frameBufferSize.width(), _frameBufferSize.height())); } - return _tertiaryFramebufferObject; + return _tertiaryFramebuffer; } -QOpenGLFramebufferObject* TextureCache::getShadowFramebufferObject() { - if (!_shadowFramebufferObject) { + +gpu::FramebufferPointer TextureCache::getShadowFramebuffer() { + if (!_shadowFramebuffer) { const int SHADOW_MAP_SIZE = 2048; - _shadowFramebufferObject = new QOpenGLFramebufferObject(SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, - QOpenGLFramebufferObject::NoAttachment, GL_TEXTURE_2D, GL_RGB); - - glGenTextures(1, &_shadowDepthTextureID); - glBindTexture(GL_TEXTURE_2D, _shadowDepthTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, - 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - const float DISTANT_BORDER[] = { 1.0f, 1.0f, 1.0f, 1.0f }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, DISTANT_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - glBindTexture(GL_TEXTURE_2D, 0); - - _shadowFramebufferObject->bind(); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _shadowDepthTextureID, 0); - _shadowFramebufferObject->release(); + _shadowFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(SHADOW_MAP_SIZE)); + + _shadowTexture = _shadowFramebuffer->getDepthStencilBuffer(); } - return _shadowFramebufferObject; + return _shadowFramebuffer; } GLuint TextureCache::getShadowDepthTextureID() { // ensure that the shadow framebuffer object is initialized before returning the depth texture id - getShadowFramebufferObject(); - return _shadowDepthTextureID; + getShadowFramebuffer(); + return gpu::GLBackend::getTextureID(_shadowTexture); } bool TextureCache::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::Resize) { QSize size = static_cast(event)->size(); - if (_primaryFramebufferObject && _primaryFramebufferObject->size() != size) { - delete _primaryFramebufferObject; - _primaryFramebufferObject = NULL; - glDeleteTextures(1, &_primaryDepthTextureID); - glDeleteTextures(1, &_primaryNormalTextureID); - glDeleteTextures(1, &_primarySpecularTextureID); - } - if (_secondaryFramebufferObject && _secondaryFramebufferObject->size() != size) { - delete _secondaryFramebufferObject; - _secondaryFramebufferObject = NULL; - } - if (_tertiaryFramebufferObject && _tertiaryFramebufferObject->size() != size) { - delete _tertiaryFramebufferObject; - _tertiaryFramebufferObject = NULL; + if (_frameBufferSize != size) { + _primaryFramebuffer.reset(); + _primaryColorTexture.reset(); + _primaryDepthTexture.reset(); + _primaryNormalTexture.reset(); + _primarySpecularTexture.reset(); + + _secondaryFramebuffer.reset(); + + _tertiaryFramebuffer.reset(); } } return false; @@ -359,17 +326,6 @@ void TextureCache::associateWithWidget(QGLWidget* widget) { _associatedWidget->installEventFilter(this); } -QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { - QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(_frameBufferSize); - - glBindTexture(GL_TEXTURE_2D, fbo->texture()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); - - return fbo; -} - Texture::Texture() { } @@ -391,27 +347,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArr _loaded = true; } - // default to white/blue/black - /* glBindTexture(GL_TEXTURE_2D, getID()); - switch (type) { - case NORMAL_TEXTURE: - loadSingleColorTexture(OPAQUE_BLUE); - break; - - case SPECULAR_TEXTURE: - loadSingleColorTexture(OPAQUE_BLACK); - break; - - case SPLAT_TEXTURE: - loadSingleColorTexture(TRANSPARENT_WHITE); - break; - - default: - loadSingleColorTexture(OPAQUE_WHITE); - break; - } - glBindTexture(GL_TEXTURE_2D, 0); - */ + std::string theName = url.toString().toStdString(); // if we have content, load it after we have our self pointer if (!content.isEmpty()) { _startedLoading = true; @@ -443,6 +379,18 @@ ImageReader::ImageReader(const QWeakPointer& texture, QNetworkReply* r _content(content) { } +std::once_flag onceListSuppoertedFormatsflag; +void listSupportedImageFormats() { + std::call_once(onceListSuppoertedFormatsflag, [](){ + auto supportedFormats = QImageReader::supportedImageFormats(); + QString formats; + foreach(const QByteArray& f, supportedFormats) { + formats += QString(f) + ","; + } + qCDebug(renderutils) << "List of supported Image formats:" << formats; + }); +} + void ImageReader::run() { QSharedPointer texture = _texture.toStrongRef(); if (texture.isNull()) { @@ -456,11 +404,29 @@ void ImageReader::run() { _content = _reply->readAll(); _reply->deleteLater(); } - QImage image = QImage::fromData(_content); + listSupportedImageFormats(); + + // try to help the QImage loader by extracting the image file format from the url filename ext + // Some tga are not created properly for example without it + auto filename = _url.fileName().toStdString(); + auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + QImage image = QImage::fromData(_content, filenameExtension.c_str()); + + // Note that QImage.format is the pixel format which is different from the "format" of the image file... + auto imageFormat = image.format(); int originalWidth = image.width(); int originalHeight = image.height(); + if (originalWidth == 0 || originalHeight == 0 || imageFormat == QImage::Format_Invalid) { + if (filenameExtension.empty()) { + qCDebug(renderutils) << "QImage failed to create from content, no file extension:" << _url; + } else { + qCDebug(renderutils) << "QImage failed to create from content" << _url; + } + return; + } + // enforce a fixed maximum area (1024 * 2048) const int MAXIMUM_AREA_SIZE = 2097152; int imageArea = image.width() * image.height(); @@ -559,7 +525,7 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, (isLinearRGB ? gpu::RGBA : gpu::SRGBA)); formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, (isLinearRGB ? gpu::BGRA : gpu::SBGRA)); } - _gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height())); + _gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); _gpuTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); _gpuTexture->autoGenerateMips(-1); } @@ -598,7 +564,7 @@ QSharedPointer DilatableNetworkTexture::getDilatedTexture(float dilatio formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, (isLinearRGB ? gpu::RGBA : gpu::SRGBA)); formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, (isLinearRGB ? gpu::BGRA : gpu::BGRA)); } - texture->_gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, dilatedImage.width(), dilatedImage.height())); + texture->_gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, dilatedImage.width(), dilatedImage.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); texture->_gpuTexture->assignStoredMip(0, formatMip, dilatedImage.byteCount(), dilatedImage.constBits()); texture->_gpuTexture->autoGenerateMips(-1); diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index 3ea46a4421..42c1cecde3 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -22,8 +23,6 @@ #include #include -class QOpenGLFramebufferObject; - class NetworkTexture; typedef QSharedPointer NetworkTexturePointer; @@ -60,11 +59,17 @@ public: /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is /// used for scene rendering. - QOpenGLFramebufferObject* getPrimaryFramebufferObject(); - + gpu::FramebufferPointer getPrimaryFramebuffer(); + + gpu::TexturePointer getPrimaryDepthTexture(); + gpu::TexturePointer getPrimaryColorTexture(); + gpu::TexturePointer getPrimaryNormalTexture(); + gpu::TexturePointer getPrimarySpecularTexture(); + /// Returns the ID of the primary framebuffer object's depth texture. This contains the Z buffer used in rendering. GLuint getPrimaryDepthTextureID(); - + GLuint getPrimaryColorTextureID(); + /// Returns the ID of the primary framebuffer object's normal texture. GLuint getPrimaryNormalTextureID(); @@ -76,15 +81,16 @@ public: /// Returns a pointer to the secondary framebuffer object, used as an additional render target when performing full /// screen effects. - QOpenGLFramebufferObject* getSecondaryFramebufferObject(); + gpu::FramebufferPointer getSecondaryFramebuffer(); /// Returns a pointer to the tertiary framebuffer object, used as an additional render target when performing full /// screen effects. - QOpenGLFramebufferObject* getTertiaryFramebufferObject(); - - /// Returns a pointer to the framebuffer object used to render shadow maps. - QOpenGLFramebufferObject* getShadowFramebufferObject(); + gpu::FramebufferPointer getTertiaryFramebuffer(); + /// Returns the framebuffer object used to render shadow maps; + gpu::FramebufferPointer getShadowFramebuffer(); + + /// Returns the ID of the shadow framebuffer object's depth texture. GLuint getShadowDepthTextureID(); @@ -99,24 +105,26 @@ private: TextureCache(); virtual ~TextureCache(); friend class DilatableNetworkTexture; - - QOpenGLFramebufferObject* createFramebufferObject(); gpu::TexturePointer _permutationNormalTexture; gpu::TexturePointer _whiteTexture; gpu::TexturePointer _blueTexture; + QHash > _dilatableNetworkTextures; - - GLuint _primaryDepthTextureID; - GLuint _primaryNormalTextureID; - GLuint _primarySpecularTextureID; - QOpenGLFramebufferObject* _primaryFramebufferObject; - QOpenGLFramebufferObject* _secondaryFramebufferObject; - QOpenGLFramebufferObject* _tertiaryFramebufferObject; - - QOpenGLFramebufferObject* _shadowFramebufferObject; - GLuint _shadowDepthTextureID; + + gpu::TexturePointer _primaryDepthTexture; + gpu::TexturePointer _primaryColorTexture; + gpu::TexturePointer _primaryNormalTexture; + gpu::TexturePointer _primarySpecularTexture; + gpu::FramebufferPointer _primaryFramebuffer; + void createPrimaryFramebuffer(); + + gpu::FramebufferPointer _secondaryFramebuffer; + gpu::FramebufferPointer _tertiaryFramebuffer; + + gpu::FramebufferPointer _shadowFramebuffer; + gpu::TexturePointer _shadowTexture; QSize _frameBufferSize; QGLWidget* _associatedWidget; diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index adf80d01bf..7648cac429 100755 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -19,7 +19,7 @@ uniform sampler2D diffuseMap; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec3 color; @@ -31,7 +31,7 @@ void main(void) { Material mat = getMaterial(); packDeferredFragment( - normalize(normal.xyz), + normalize(interpolatedNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), getMaterialDiffuse(mat) * diffuse.rgb * color, getMaterialSpecular(mat), diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index f4511da944..97b5eb640b 100755 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -20,7 +20,7 @@ const int MAX_TEXCOORDS = 2; uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec3 color; @@ -36,7 +36,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> - <$transformModelToEyeDir(cam, obj, gl_Normal, normal.xyz)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> - normal = vec4(normalize(normal.xyz), 0.0); + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_lightmap.slf b/libraries/render-utils/src/model_lightmap.slf index b7660da16d..00d74ed997 100755 --- a/libraries/render-utils/src/model_lightmap.slf +++ b/libraries/render-utils/src/model_lightmap.slf @@ -24,7 +24,7 @@ uniform sampler2D emissiveMap; uniform vec2 emissiveParams; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec3 color; @@ -39,7 +39,7 @@ void main(void) { Material mat = getMaterial(); packDeferredFragmentLightmap( - normalize(normal.xyz), + normalize(interpolatedNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), getMaterialDiffuse(mat) * diffuse.rgb * color, getMaterialSpecular(mat), diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index 56b34a5aa8..54d9bd21e1 100755 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -23,7 +23,7 @@ uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; attribute vec2 texcoord1; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; // the interpolated texcoord1 varying vec2 interpolatedTexcoord1; @@ -44,8 +44,8 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> - <$transformModelToEyeDir(cam, obj, gl_Normal, normal.xyz)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> - normal = vec4(normalize(normal.xyz), 0.0); + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_lightmap_specular_map.slf b/libraries/render-utils/src/model_lightmap_specular_map.slf index e4bb682601..daabfe2d07 100755 --- a/libraries/render-utils/src/model_lightmap_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_specular_map.slf @@ -27,7 +27,7 @@ uniform vec2 emissiveParams; uniform sampler2D specularMap; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec2 interpolatedTexcoord1; @@ -42,7 +42,7 @@ void main(void) { Material mat = getMaterial(); packDeferredFragmentLightmap( - normalize(normal.xyz), + normalize(interpolatedNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), getMaterialDiffuse(mat) * diffuse.rgb * color, specular, // no use of getMaterialSpecular(mat) diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index e702b446b8..2f493053dd 100755 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -1,5 +1,5 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> +<@include gpu/Config.slh@> +<$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // // model.vert @@ -12,9 +12,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> const int MAX_TEXCOORDS = 2; @@ -29,7 +29,7 @@ varying vec4 interpolatedNormal; // the interpolated tangent varying vec4 interpolatedTangent; -varying vec3 color; +varying vec3 color; void main(void) { // transform and store the normal and tangent for interpolation @@ -42,13 +42,13 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> - <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> - <$transformModelToEyeDir(cam, obj, tangent, interpolatedTangent.xyz)$> - - interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> + <$transformModelToEyeDir(cam, obj, tangent, interpolatedTangent.xyz)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_specular_map.slf b/libraries/render-utils/src/model_specular_map.slf index a0203d74af..46f565de6c 100755 --- a/libraries/render-utils/src/model_specular_map.slf +++ b/libraries/render-utils/src/model_specular_map.slf @@ -23,7 +23,7 @@ uniform sampler2D diffuseMap; uniform sampler2D specularMap; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec3 color; @@ -35,7 +35,7 @@ void main(void) { Material mat = getMaterial(); packDeferredFragment( - normalize(normal.xyz), + normalize(interpolatedNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), getMaterialDiffuse(mat) * diffuse.rgb * color, specular, //getMaterialSpecular(mat), diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 9b8eb97f70..9b34951f88 100755 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -19,7 +19,7 @@ // the diffuse texture uniform sampler2D diffuseMap; -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec3 color; @@ -31,7 +31,7 @@ void main(void) { Material mat = getMaterial(); packDeferredFragmentTranslucent( - normalize(normal.xyz), + normalize(interpolatedNormal.xyz), getMaterialOpacity(mat) * diffuse.a, getMaterialDiffuse(mat) * diffuse.rgb * color, getMaterialSpecular(mat), diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 3e70674e33..4e1a09fb92 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -15,7 +15,7 @@ <@include DeferredBufferWrite.slh@> // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; // the glow intensity //uniform float glowIntensity; @@ -28,7 +28,7 @@ void main(void) { */ packDeferredFragment( - normalize(normal.xyz), + normalize(interpolatedNormal.xyz), glowIntensity, gl_Color.rgb, gl_FrontMaterial.specular.rgb, diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 61338b440d..9ad47a3e66 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -13,11 +13,11 @@ // // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; void main(void) { // transform and store the normal for interpolation - normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0)); + interpolatedNormal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0)); // pass along the diffuse color gl_FrontColor = gl_Color; diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index f475cbd1f5..c94fc0d151 100755 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -1,5 +1,5 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> +<@include gpu/Config.slh@> +<$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // // skin_model.vert @@ -12,8 +12,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> const int MAX_TEXCOORDS = 2; const int MAX_CLUSTERS = 128; @@ -26,18 +26,18 @@ attribute vec4 clusterIndices; attribute vec4 clusterWeights; // the interpolated normal -varying vec4 normal; +varying vec4 interpolatedNormal; varying vec3 color; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); - normal = vec4(0.0, 0.0, 0.0, 0.0); + interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0); for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[int(clusterIndices[i])]; float clusterWeight = clusterWeights[i]; position += clusterMatrix * gl_Vertex * clusterWeight; - normal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight; + interpolatedNormal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight; } // pass along the diffuse color @@ -46,11 +46,11 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, position, gl_Position)$> - <$transformModelToEyeDir(cam, obj, normal.xyz, normal.xyz)$> - - normal = vec4(normalize(normal.xyz), 0.0); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, position, gl_Position)$> + <$transformModelToEyeDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); } diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index bbc8f81d12..ed552a7aca 100755 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -1,5 +1,5 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> +<@include gpu/Config.slh@> +<$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // // skin_model_normal_map.vert @@ -12,8 +12,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> const int MAX_TEXCOORDS = 2; const int MAX_CLUSTERS = 128; @@ -54,16 +54,16 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, interpolatedPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> - <$transformModelToEyeDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> - - interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, interpolatedPosition, gl_Position)$> + <$transformModelToEyeDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> + <$transformModelToEyeDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); } diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index deb1e1e9e9..e210ee6f6e 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -9,9 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AudioScriptingInterface.h" + #include "ScriptAudioInjector.h" #include "ScriptEngineLogging.h" -#include "AudioScriptingInterface.h" void registerAudioMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); @@ -69,3 +70,27 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const Audi return NULL; } } + +void AudioScriptingInterface::injectGeneratedNoise(bool inject) { + if (_localAudioInterface) { + _localAudioInterface->enableAudioSourceInject(inject); + } +} + +void AudioScriptingInterface::selectPinkNoise() { + if (_localAudioInterface) { + _localAudioInterface->selectAudioSourcePinkNoise(); + } +} + +void AudioScriptingInterface::selectSine440() { + if (_localAudioInterface) { + _localAudioInterface->selectAudioSourceSine440(); + } +} + +void AudioScriptingInterface::setStereoInput(bool stereo) { + if (_localAudioInterface) { + _localAudioInterface->setIsStereoInput(stereo); + } +} diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index b74c520670..bbc9a57db8 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -29,6 +29,12 @@ protected: // this method is protected to stop C++ callers from calling, but invokable from script Q_INVOKABLE ScriptAudioInjector* playSound(Sound* sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); + Q_INVOKABLE void injectGeneratedNoise(bool inject); + Q_INVOKABLE void selectPinkNoise(); + Q_INVOKABLE void selectSine440(); + + Q_INVOKABLE void setStereoInput(bool stereo); + signals: void mutedByMixer(); void environmentMuted(); diff --git a/libraries/script-engine/src/SceneScriptingInterface.cpp b/libraries/script-engine/src/SceneScriptingInterface.cpp index 5a8f591410..2461487414 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.cpp +++ b/libraries/script-engine/src/SceneScriptingInterface.cpp @@ -46,22 +46,46 @@ int SceneScriptingInterface::getStageYearTime() const { return _skyStage->getYearTime(); } -void SceneScriptingInterface::setSunColor(const glm::vec3& color) { +void SceneScriptingInterface::setKeyLightColor(const glm::vec3& color) { _skyStage->setSunColor(color); } -const glm::vec3& SceneScriptingInterface::getSunColor() const { +glm::vec3 SceneScriptingInterface::getKeyLightColor() const { return _skyStage->getSunColor(); } -void SceneScriptingInterface::setSunIntensity(float intensity) { +void SceneScriptingInterface::setKeyLightIntensity(float intensity) { _skyStage->setSunIntensity(intensity); } -float SceneScriptingInterface::getSunIntensity() const { +float SceneScriptingInterface::getKeyLightIntensity() const { return _skyStage->getSunIntensity(); } +void SceneScriptingInterface::setKeyLightAmbientIntensity(float intensity) { + _skyStage->setSunAmbientIntensity(intensity); +} + +float SceneScriptingInterface::getKeyLightAmbientIntensity() const { + return _skyStage->getSunAmbientIntensity(); +} + +void SceneScriptingInterface::setKeyLightDirection(const glm::vec3& direction) { + _skyStage->setSunDirection(direction); +} + +glm::vec3 SceneScriptingInterface::getKeyLightDirection() const { + return _skyStage->getSunDirection(); +} + +void SceneScriptingInterface::setStageSunModelEnable(bool isEnabled) { + _skyStage->setSunModelEnable(isEnabled); +} + +bool SceneScriptingInterface::isStageSunModelEnabled() const { + return _skyStage->isSunModelEnabled(); +} + model::SunSkyStagePointer SceneScriptingInterface::getSkyStage() const { return _skyStage; } diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 352bc1e78f..c384153a0f 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -38,10 +38,26 @@ public: Q_INVOKABLE void setStageYearTime(int day); Q_INVOKABLE int getStageYearTime() const; - Q_INVOKABLE void setSunColor(const glm::vec3& color); - Q_INVOKABLE const glm::vec3& getSunColor() const; - Q_INVOKABLE void setSunIntensity(float intensity); - Q_INVOKABLE float getSunIntensity() const; + // Enable/disable the stage sun model which uses the key light to simulate + // the sun light based on the location of the stage trelative to earth and the current time + Q_INVOKABLE void setStageSunModelEnable(bool isEnabled); + Q_INVOKABLE bool isStageSunModelEnabled() const; + + + Q_INVOKABLE void setKeyLightColor(const glm::vec3& color); + Q_INVOKABLE glm::vec3 getKeyLightColor() const; + Q_INVOKABLE void setKeyLightIntensity(float intensity); + Q_INVOKABLE float getKeyLightIntensity() const; + Q_INVOKABLE void setKeyLightAmbientIntensity(float intensity); + Q_INVOKABLE float getKeyLightAmbientIntensity() const; + + // setKeyLightDIrection is only effective if stage Sun model is disabled + Q_INVOKABLE void setKeyLightDirection(const glm::vec3& direction); + + Q_INVOKABLE glm::vec3 getKeyLightDirection() const; + + + model::SunSkyStagePointer getSkyStage() const; diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/script-engine/src/ScriptAudioInjector.cpp index a9cf40558c..2ec30ad4dd 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/script-engine/src/ScriptAudioInjector.cpp @@ -13,8 +13,12 @@ #include "ScriptAudioInjector.h" QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in) { + // The AudioScriptingInterface::playSound method can return null, so we need to account for that. + if (!in) { + return QScriptValue(QScriptValue::NullValue); + } + // when the script goes down we want to cleanup the injector - QObject::connect(engine, &QScriptEngine::destroyed, in, &ScriptAudioInjector::stopInjectorImmediately, Qt::DirectConnection); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2f9427a63d..ac2c212001 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -647,8 +647,10 @@ void ScriptEngine::stopAllTimers() { } void ScriptEngine::stop() { - _isFinished = true; - emit runningStateChanged(); + if (!_isFinished) { + _isFinished = true; + emit runningStateChanged(); + } } void ScriptEngine::timerFired() { diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 4e8fb7d3cd..5d1b70275c 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -203,6 +203,13 @@ glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { return glm::angleAxis(angle, axis); } +bool isPointBehindTrianglesPlane(glm::vec3 point, glm::vec3 p0, glm::vec3 p1, glm::vec3 p2) { + glm::vec3 v1 = p0 - p1, v2 = p2 - p1; // Non-collinear vectors contained in the plane + glm::vec3 n = glm::cross(v1, v2); // Plane's normal vector, pointing out of the triangle + float d = -glm::dot(n, p0); // Compute plane's equation constant + return (glm::dot(n, point) + d) >= 0; +} + glm::vec3 extractTranslation(const glm::mat4& matrix) { return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]); } diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 50393b7f5f..dda57a9cd9 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -17,6 +17,17 @@ #include #include +// Bring the most commonly used GLM types into the default namespace +using glm::ivec3; +using glm::ivec2; +using glm::uvec2; +using glm::mat3; +using glm::mat4; +using glm::vec2; +using glm::vec3; +using glm::vec4; +using glm::quat; + #include #include #include @@ -71,6 +82,8 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2); glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); +bool isPointBehindTrianglesPlane(glm::vec3 point, glm::vec3 p0, glm::vec3 p1, glm::vec3 p2); + glm::vec3 extractTranslation(const glm::mat4& matrix); void setTranslation(glm::mat4& matrix, const glm::vec3& translation); diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index bf846c0bf2..84c8ae4939 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -14,7 +14,7 @@ #include #include #include - +#include #include "PathUtils.h" @@ -24,6 +24,7 @@ QString& PathUtils::resourcesPath() { #else static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; #endif + return staticResourcePath; } diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 116fc430c2..bc3e2edb6d 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -17,16 +17,23 @@ class OctreeRenderer; class RenderArgs { public: - enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE, DEBUG_RENDER_MODE }; + enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE }; enum RenderSide { MONO, STEREO_LEFT, STEREO_RIGHT }; + enum DebugFlags { + RENDER_DEBUG_NONE=0, + RENDER_DEBUG_HULLS=1, + RENDER_DEBUG_SIMULATION_OWNERSHIP=2 + }; + OctreeRenderer* _renderer; ViewFrustum* _viewFrustum; float _sizeScale; int _boundaryLevelAdjust; RenderMode _renderMode; RenderSide _renderSide; + DebugFlags _debugFlags; int _elementsTouched; int _itemsRendered; diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 5fe1fc230d..1c86f109c5 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -37,12 +37,6 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString _halfExtents = glm::vec3(radius); break; } - case SHAPE_TYPE_CONVEX_HULL: - _url = QUrl(url); - // halfExtents aren't used by convex-hull or compound convex-hull except as part of - // the generation of the key for the ShapeManager. - _halfExtents = halfExtents; - break; case SHAPE_TYPE_COMPOUND: _url = QUrl(url); _halfExtents = halfExtents; @@ -78,12 +72,8 @@ void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) { } void ShapeInfo::setConvexHulls(const QVector>& points) { - if (points.size() == 1) { - _type = SHAPE_TYPE_CONVEX_HULL; - } else { - _type = SHAPE_TYPE_COMPOUND; - } _points = points; + _type = (_points.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; _doubleHashKey.clear(); } @@ -95,6 +85,14 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) { _doubleHashKey.clear(); } +uint32_t ShapeInfo::getNumSubShapes() const { + if (_type == SHAPE_TYPE_NONE) { + return 0; + } else if (_type == SHAPE_TYPE_COMPOUND) { + return _points.size(); + } + return 1; +} float ShapeInfo::computeVolume() const { const float DEFAULT_VOLUME = 1.0f; float volume = DEFAULT_VOLUME; @@ -116,7 +114,7 @@ float ShapeInfo::computeVolume() const { } case SHAPE_TYPE_CAPSULE_Y: { float radius = _halfExtents.x; - volume = PI * radius * radius * (2.0f * _halfExtents.y + 4.0f * radius / 3.0f); + volume = PI * radius * radius * (2.0f * (_halfExtents.y - _halfExtents.x) + 4.0f * radius / 3.0f); break; } default: @@ -126,6 +124,54 @@ float ShapeInfo::computeVolume() const { return volume; } +bool ShapeInfo::contains(const glm::vec3& point) const { + switch(_type) { + case SHAPE_TYPE_SPHERE: + return glm::length(point) <= _halfExtents.x; + case SHAPE_TYPE_ELLIPSOID: { + glm::vec3 scaledPoint = glm::abs(point) / _halfExtents; + return glm::length(scaledPoint) <= 1.0f; + } + case SHAPE_TYPE_CYLINDER_X: + return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; + case SHAPE_TYPE_CYLINDER_Y: + return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.x; + case SHAPE_TYPE_CYLINDER_Z: + return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y; + case SHAPE_TYPE_CAPSULE_X: { + if (glm::abs(point.x) <= _halfExtents.x) { + return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; + } else { + glm::vec3 absPoint = glm::abs(point) - _halfExtents.x; + return glm::length(absPoint) <= _halfExtents.z; + } + } + case SHAPE_TYPE_CAPSULE_Y: { + if (glm::abs(point.y) <= _halfExtents.y) { + return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.x; + } else { + glm::vec3 absPoint = glm::abs(point) - _halfExtents.y; + return glm::length(absPoint) <= _halfExtents.x; + } + } + case SHAPE_TYPE_CAPSULE_Z: { + if (glm::abs(point.z) <= _halfExtents.z) { + return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y; + } else { + glm::vec3 absPoint = glm::abs(point) - _halfExtents.z; + return glm::length(absPoint) <= _halfExtents.y; + } + } + case SHAPE_TYPE_BOX: + default: { + glm::vec3 absPoint = glm::abs(point); + return absPoint.x <= _halfExtents.x + && absPoint.y <= _halfExtents.y + && absPoint.z <= _halfExtents.z; + } + } +} + const DoubleHashKey& ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) { diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 4dce121d64..0bfc91c9c5 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -24,7 +24,6 @@ enum ShapeType { SHAPE_TYPE_BOX, SHAPE_TYPE_SPHERE, SHAPE_TYPE_ELLIPSOID, - SHAPE_TYPE_CONVEX_HULL, SHAPE_TYPE_PLANE, SHAPE_TYPE_COMPOUND, SHAPE_TYPE_CAPSULE_X, @@ -47,16 +46,21 @@ public: void setConvexHulls(const QVector>& points); void setCapsuleY(float radius, float halfHeight); - const int getType() const { return _type; } + int getType() const { return _type; } const glm::vec3& getHalfExtents() const { return _halfExtents; } const QVector>& getPoints() const { return _points; } + uint32_t getNumSubShapes() const; void clearPoints () { _points.clear(); } void appendToPoints (const QVector& newPoints) { _points << newPoints; } float computeVolume() const; + + /// Returns whether point is inside the shape + /// For compound shapes it will only return whether it is inside the bounding box + bool contains(const glm::vec3& point) const; const DoubleHashKey& getHash() const; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 9cf76dd1dc..8d55a0f82b 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -43,6 +43,17 @@ inline QDebug& operator<<(QDebug& dbg, const xColor& c) { return dbg; } +inline bool operator==(const xColor& lhs, const xColor& rhs) +{ + return (lhs.red == rhs.red) && (lhs.green == rhs.green) && (lhs.blue == rhs.blue); +} + +inline bool operator!=(const xColor& lhs, const xColor& rhs) +{ + return (lhs.red != rhs.red) || (lhs.green != rhs.green) || (lhs.blue != rhs.blue); +} + + static const float ZERO = 0.0f; static const float ONE = 1.0f; static const float ONE_HALF = 0.5f; diff --git a/libraries/shared/src/SimpleMovingAverage.cpp b/libraries/shared/src/SimpleMovingAverage.cpp index 90a9509c91..e1c9a27390 100644 --- a/libraries/shared/src/SimpleMovingAverage.cpp +++ b/libraries/shared/src/SimpleMovingAverage.cpp @@ -55,6 +55,6 @@ float SimpleMovingAverage::getEventDeltaAverage() const { (WEIGHTING * ((usecTimestampNow() - _lastEventTimestamp) / 1000000.0f)); } -float SimpleMovingAverage::getAverageSampleValuePerSecond() const { - return _average * (1.0f / getEventDeltaAverage()); +uint64_t SimpleMovingAverage::getUsecsSinceLastEvent() const { + return usecTimestampNow() - _lastEventTimestamp; } diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 3eec9d5be8..194a078194 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -25,8 +25,11 @@ public: int getSampleCount() const { return _numSamples; }; float getAverage() const { return _average; }; - float getEventDeltaAverage() const; - float getAverageSampleValuePerSecond() const; + float getEventDeltaAverage() const; // returned in seconds + float getAverageSampleValuePerSecond() const { return _average * (1.0f / getEventDeltaAverage()); } + + uint64_t getUsecsSinceLastEvent() const; + private: int _numSamples; uint64_t _lastEventTimestamp; diff --git a/libraries/shared/src/ThreadHelpers.h b/libraries/shared/src/ThreadHelpers.h new file mode 100644 index 0000000000..cc0e1ee666 --- /dev/null +++ b/libraries/shared/src/ThreadHelpers.h @@ -0,0 +1,29 @@ +// +// ThreadHelpers.h +// +// Created by Bradley Austin Davis on 2015-04-04 +// 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 +// +#pragma once +#ifndef hifi_ThreadHelpers_h +#define hifi_ThreadHelpers_h + +#include +#include +#include + +template +void withLock(L lock, F function) { + throw std::exception(); +} + +template +void withLock(QMutex& lock, F function) { + QMutexLocker locker(&lock); + function(); +} + +#endif diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt new file mode 100644 index 0000000000..36a0a1a846 --- /dev/null +++ b/libraries/ui/CMakeLists.txt @@ -0,0 +1,12 @@ +set(TARGET_NAME ui) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library(OpenGL Network Qml Quick Script) + +link_hifi_libraries(render-utils shared) + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + + diff --git a/libraries/ui/src/MessageDialog.cpp b/libraries/ui/src/MessageDialog.cpp new file mode 100644 index 0000000000..695f87552a --- /dev/null +++ b/libraries/ui/src/MessageDialog.cpp @@ -0,0 +1,119 @@ +// +// MessageDialog.cpp +// +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "MessageDialog.h" + +HIFI_QML_DEF(MessageDialog) + +MessageDialog::MessageDialog(QQuickItem* parent) : OffscreenQmlDialog(parent) { + _buttons = StandardButtons(Ok | Cancel); +} + +MessageDialog::~MessageDialog() { +} + +QString MessageDialog::text() const { + return _text; +} + +QString MessageDialog::informativeText() const { + return _informativeText; +} + +QString MessageDialog::detailedText() const { + return _detailedText; +} + +MessageDialog::Icon MessageDialog::icon() const { + return _icon; +} + +void MessageDialog::setVisible(bool v) { + OffscreenQmlDialog::setVisible(v); +} + +void MessageDialog::setText(const QString& arg) { + if (arg != _text) { + _text = arg; + emit textChanged(); + } +} + +void MessageDialog::setInformativeText(const QString& arg) { + if (arg != _informativeText) { + _informativeText = arg; + emit informativeTextChanged(); + } +} + +void MessageDialog::setDetailedText(const QString& arg) { + if (arg != _detailedText) { + _detailedText = arg; + emit detailedTextChanged(); + } +} + +void MessageDialog::setIcon(MessageDialog::Icon icon) { + if (icon != _icon) { + _icon = icon; + emit iconChanged(); + } +} + +void MessageDialog::setStandardButtons(StandardButtons buttons) { + if (buttons != _buttons) { + _buttons = buttons; + emit standardButtonsChanged(); + } +} + +void MessageDialog::click(StandardButton button) { + // FIXME try to do it more like the standard dialog + click(StandardButton(button), ButtonRole::NoRole); +} + +MessageDialog::StandardButtons MessageDialog::standardButtons() const { + return _buttons; +} + +MessageDialog::StandardButton MessageDialog::clickedButton() const { + return _clickedButton; +} + +void MessageDialog::click(StandardButton button, ButtonRole) { + _clickedButton = button; + if (_resultCallback) { + _resultCallback(QMessageBox::StandardButton(_clickedButton)); + } + hide(); +} + +void MessageDialog::accept() { + // enter key is treated like OK + if (_clickedButton == NoButton) + _clickedButton = Ok; + if (_resultCallback) { + _resultCallback(QMessageBox::StandardButton(_clickedButton)); + } + OffscreenQmlDialog::accept(); +} + +void MessageDialog::reject() { + // escape key is treated like cancel + if (_clickedButton == NoButton) + _clickedButton = Cancel; + if (_resultCallback) { + _resultCallback(QMessageBox::StandardButton(_clickedButton)); + } + OffscreenQmlDialog::reject(); +} + +void MessageDialog::setResultCallback(OffscreenUi::ButtonCallback callback) { + _resultCallback = callback; +} diff --git a/libraries/ui/src/MessageDialog.h b/libraries/ui/src/MessageDialog.h new file mode 100644 index 0000000000..461bca8251 --- /dev/null +++ b/libraries/ui/src/MessageDialog.h @@ -0,0 +1,100 @@ +// +// MessageDialog.h +// +// Created by Bradley Austin Davis on 2015/04/14 +// 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 +// + +#pragma once +#ifndef hifi_MessageDialog_h +#define hifi_MessageDialog_h + +#include "OffscreenQmlDialog.h" + +class MessageDialog : public OffscreenQmlDialog +{ + Q_OBJECT + HIFI_QML_DECL + +private: + Q_ENUMS(Icon) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QString informativeText READ informativeText WRITE setInformativeText NOTIFY informativeTextChanged) + Q_PROPERTY(QString detailedText READ detailedText WRITE setDetailedText NOTIFY detailedTextChanged) + Q_PROPERTY(Icon icon READ icon WRITE setIcon NOTIFY iconChanged) + Q_PROPERTY(StandardButtons standardButtons READ standardButtons WRITE setStandardButtons NOTIFY standardButtonsChanged) + Q_PROPERTY(StandardButton clickedButton READ clickedButton NOTIFY buttonClicked) + +public: + enum Icon { NoIcon, Information, Warning, Critical, Question }; + + enum ButtonRole { + // keep this in sync with QDialogButtonBox::ButtonRole and QPlatformDialogHelper::ButtonRole + InvalidRole = -1, + AcceptRole, + RejectRole, + DestructiveRole, + ActionRole, + HelpRole, + YesRole, + NoRole, + ResetRole, + ApplyRole, + + NRoles + }; + + MessageDialog(QQuickItem* parent = 0); + virtual ~MessageDialog(); + + QString text() const; + QString informativeText() const; + QString detailedText() const; + Icon icon() const; + +public slots: + virtual void setVisible(bool v); + void setText(const QString& arg); + void setInformativeText(const QString& arg); + void setDetailedText(const QString& arg); + void setIcon(Icon icon); + void setStandardButtons(StandardButtons buttons); + void setResultCallback(OffscreenUi::ButtonCallback callback); + void click(StandardButton button); + StandardButtons standardButtons() const; + StandardButton clickedButton() const; + +signals: + void textChanged(); + void informativeTextChanged(); + void detailedTextChanged(); + void iconChanged(); + void standardButtonsChanged(); + void buttonClicked(); + void discard(); + void help(); + void yes(); + void no(); + void apply(); + void reset(); + +protected slots: + virtual void click(StandardButton button, ButtonRole); + virtual void accept(); + virtual void reject(); + +private: + QString _title; + QString _text; + QString _informativeText; + QString _detailedText; + Icon _icon{ Information }; + StandardButtons _buttons; + StandardButton _clickedButton{ NoButton }; + OffscreenUi::ButtonCallback _resultCallback; +}; + +#endif // hifi_MessageDialog_h diff --git a/libraries/ui/src/OffscreenQmlDialog.cpp b/libraries/ui/src/OffscreenQmlDialog.cpp new file mode 100644 index 0000000000..eba81f708b --- /dev/null +++ b/libraries/ui/src/OffscreenQmlDialog.cpp @@ -0,0 +1,42 @@ +// +// OffscreenQmlDialog.cpp +// +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OffscreenQmlDialog.h" + +OffscreenQmlDialog::OffscreenQmlDialog(QQuickItem* parent) + : QQuickItem(parent) { } + +OffscreenQmlDialog::~OffscreenQmlDialog() { +} + +void OffscreenQmlDialog::hide() { + static_cast(parent())->setEnabled(false); +} + +QString OffscreenQmlDialog::title() const { + return _title; +} + +void OffscreenQmlDialog::setTitle(const QString& title) { + if (title != _title) { + _title = title; + emit titleChanged(); + } +} + +void OffscreenQmlDialog::accept() { + hide(); + emit accepted(); +} + +void OffscreenQmlDialog::reject() { + hide(); + emit rejected(); +} diff --git a/libraries/ui/src/OffscreenQmlDialog.h b/libraries/ui/src/OffscreenQmlDialog.h new file mode 100644 index 0000000000..33201c385d --- /dev/null +++ b/libraries/ui/src/OffscreenQmlDialog.h @@ -0,0 +1,76 @@ +// +// OffscreenQmlDialog.h +// +// Created by Bradley Austin Davis on 2015/04/14 +// 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 +// + +#pragma once +#ifndef hifi_OffscreenQmlDialog_h +#define hifi_OffscreenQmlDialog_h + +#include + +#include "OffscreenUi.h" + +class OffscreenQmlDialog : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + Q_ENUMS(StandardButton) + Q_FLAGS(StandardButtons) + +public: + OffscreenQmlDialog(QQuickItem* parent = nullptr); + virtual ~OffscreenQmlDialog(); + + enum StandardButton { + // keep this in sync with QDialogButtonBox::StandardButton and QMessageBox::StandardButton + NoButton = 0x00000000, + Ok = 0x00000400, + Save = 0x00000800, + SaveAll = 0x00001000, + Open = 0x00002000, + Yes = 0x00004000, + YesToAll = 0x00008000, + No = 0x00010000, + NoToAll = 0x00020000, + Abort = 0x00040000, + Retry = 0x00080000, + Ignore = 0x00100000, + Close = 0x00200000, + Cancel = 0x00400000, + Discard = 0x00800000, + Help = 0x01000000, + Apply = 0x02000000, + Reset = 0x04000000, + RestoreDefaults = 0x08000000, + NButtons + }; + Q_DECLARE_FLAGS(StandardButtons, StandardButton) + +protected: + void hide(); + virtual void accept(); + virtual void reject(); + +public: + QString title() const; + void setTitle(const QString& title); + +signals: + void accepted(); + void rejected(); + void titleChanged(); + +private: + QString _title; + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(OffscreenQmlDialog::StandardButtons) + +#endif diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp new file mode 100644 index 0000000000..d9c7cd890d --- /dev/null +++ b/libraries/ui/src/OffscreenUi.cpp @@ -0,0 +1,480 @@ +// +// OffscreenUi.cpp +// interface/src/render-utils +// +// Created by Bradley Austin Davis on 2015-04-04 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "OffscreenUi.h" +#include +#include +#include +#include +#include "MessageDialog.h" + + +Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) +Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") + +// Time between receiving a request to render the offscreen UI actually triggering +// the render. Could possibly be increased depending on the framerate we expect to +// achieve. +static const int SMALL_INTERVAL = 5; + +class OffscreenUiRoot : public QQuickItem { + Q_OBJECT +public: + + OffscreenUiRoot(QQuickItem* parent = 0); + Q_INVOKABLE void information(const QString& title, const QString& text); + Q_INVOKABLE void loadChild(const QUrl& url) { + DependencyManager::get()->load(url); + } +}; + + +OffscreenUiRoot::OffscreenUiRoot(QQuickItem* parent) : QQuickItem(parent) { +} + +void OffscreenUiRoot::information(const QString& title, const QString& text) { + OffscreenUi::information(title, text); +} + +OffscreenUi::OffscreenUi() { + ::qmlRegisterType("Hifi", 1, 0, "Root"); +} + +OffscreenUi::~OffscreenUi() { + // Make sure the context is current while doing cleanup. Note that we use the + // offscreen surface here because passing 'this' at this point is not safe: the + // underlying platform window may already be destroyed. To avoid all the trouble, use + // another surface that is valid for sure. + makeCurrent(); + + // Delete the render control first since it will free the scenegraph resources. + // Destroy the QQuickWindow only afterwards. + delete _renderControl; + + delete _qmlComponent; + delete _quickWindow; + delete _qmlEngine; + + doneCurrent(); +} + +void OffscreenUi::create(QOpenGLContext* shareContext) { + OffscreenGlCanvas::create(shareContext); + + makeCurrent(); + + // Create a QQuickWindow that is associated with out render control. Note that this + // window never gets created or shown, meaning that it will never get an underlying + // native (platform) window. + QQuickWindow::setDefaultAlphaBuffer(true); + _quickWindow = new QQuickWindow(_renderControl); + _quickWindow->setColor(QColor(255, 255, 255, 0)); + _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); + // Create a QML engine. + _qmlEngine = new QQmlEngine; + if (!_qmlEngine->incubationController()) { + _qmlEngine->setIncubationController(_quickWindow->incubationController()); + } + + // When Quick says there is a need to render, we will not render immediately. Instead, + // a timer with a small interval is used to get better performance. + _updateTimer.setSingleShot(true); + _updateTimer.setInterval(SMALL_INTERVAL); + connect(&_updateTimer, &QTimer::timeout, this, &OffscreenUi::updateQuick); + + // Now hook up the signals. For simplicy we don't differentiate between + // renderRequested (only render is needed, no sync) and sceneChanged (polish and sync + // is needed too). + connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenUi::requestRender); + connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate); + +#ifdef DEBUG + connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{ + qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject(); + }); + connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] { + qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem(); + }); +#endif + + _qmlComponent = new QQmlComponent(_qmlEngine); + // Initialize the render control and our OpenGL resources. + makeCurrent(); + _renderControl->initialize(&_context); +} + +void OffscreenUi::addImportPath(const QString& path) { + _qmlEngine->addImportPath(path); +} + +void OffscreenUi::resize(const QSize& newSize) { + makeCurrent(); + + // Clear out any fbos with the old size + qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0; + qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; + _fboCache.setSize(newSize * pixelRatio); + + if (_quickWindow) { + _quickWindow->setGeometry(QRect(QPoint(), newSize)); + _quickWindow->contentItem()->setSize(newSize); + } + + + // Update our members + if (_rootItem) { + _rootItem->setSize(newSize); + } + + doneCurrent(); +} + +QQuickItem* OffscreenUi::getRootItem() { + return _rootItem; +} + +void OffscreenUi::setBaseUrl(const QUrl& baseUrl) { + _qmlEngine->setBaseUrl(baseUrl); +} + +QObject* OffscreenUi::load(const QUrl& qmlSource, std::function f) { + _qmlComponent->loadUrl(qmlSource); + if (_qmlComponent->isLoading()) { + connect(_qmlComponent, &QQmlComponent::statusChanged, this, + [this, f](QQmlComponent::Status){ + finishQmlLoad(f); + }); + return nullptr; + } + + return finishQmlLoad(f); +} + +void OffscreenUi::requestUpdate() { + _polish = true; + if (!_updateTimer.isActive()) { + _updateTimer.start(); + } +} + +void OffscreenUi::requestRender() { + if (!_updateTimer.isActive()) { + _updateTimer.start(); + } +} + +QObject* OffscreenUi::finishQmlLoad(std::function f) { + disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); + if (_qmlComponent->isError()) { + QList errorList = _qmlComponent->errors(); + foreach(const QQmlError& error, errorList) { + qWarning() << error.url() << error.line() << error; + } + return nullptr; + } + + QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp); + QObject* newObject = _qmlComponent->beginCreate(newContext); + if (_qmlComponent->isError()) { + QList errorList = _qmlComponent->errors(); + foreach(const QQmlError& error, errorList) + qWarning() << error.url() << error.line() << error; + if (!_rootItem) { + qFatal("Unable to finish loading QML root"); + } + return nullptr; + } + + f(newContext, newObject); + _qmlComponent->completeCreate(); + + + // All quick items should be focusable + QQuickItem* newItem = qobject_cast(newObject); + if (newItem) { + // Make sure we make items focusable (critical for + // supporting keyboard shortcuts) + newItem->setFlag(QQuickItem::ItemIsFocusScope, true); + } + + // If we already have a root, just set a couple of flags and the ancestry + if (_rootItem) { + // Allow child windows to be destroyed from JS + QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); + newObject->setParent(_rootItem); + if (newItem) { + newItem->setParentItem(_rootItem); + } + return newObject; + } + + if (!newItem) { + qFatal("Could not load object as root item"); + return nullptr; + } + // The root item is ready. Associate it with the window. + _rootItem = newItem; + _rootItem->setParentItem(_quickWindow->contentItem()); + _rootItem->setSize(_quickWindow->renderTargetSize()); + return _rootItem; +} + + +void OffscreenUi::updateQuick() { + if (_paused) { + return; + } + if (!makeCurrent()) { + return; + } + + // Polish, synchronize and render the next frame (into our fbo). In this example + // everything happens on the same thread and therefore all three steps are performed + // in succession from here. In a threaded setup the render() call would happen on a + // separate thread. + if (_polish) { + _renderControl->polishItems(); + _renderControl->sync(); + _polish = false; + } + + QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo(); + + _quickWindow->setRenderTarget(fbo); + fbo->bind(); + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + _renderControl->render(); + + Q_ASSERT(!glGetError()); + + _quickWindow->resetOpenGLState(); + + QOpenGLFramebufferObject::bindDefault(); + // Force completion of all the operations before we emit the texture as being ready for use + glFinish(); + + emit textureUpdated(fbo->texture()); +} + +QPointF OffscreenUi::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) { + vec2 sourceSize; + if (dynamic_cast(sourceObject)) { + sourceSize = toGlm(((QWidget*)sourceObject)->size()); + } else if (dynamic_cast(sourceObject)) { + sourceSize = toGlm(((QWindow*)sourceObject)->size()); + } + vec2 offscreenPosition = toGlm(sourcePosition); + offscreenPosition /= sourceSize; + offscreenPosition *= vec2(toGlm(_quickWindow->size())); + return QPointF(offscreenPosition.x, offscreenPosition.y); +} + +// This hack allows the QML UI to work with keys that are also bound as +// shortcuts at the application level. However, it seems as though the +// bound actions are still getting triggered. At least for backspace. +// Not sure why. +// +// However, the problem may go away once we switch to the new menu system, +// so I think it's OK for the time being. +bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { + Q_ASSERT(event->type() == QEvent::ShortcutOverride); + QObject* focusObject = _quickWindow->focusObject(); + if (focusObject != _quickWindow && focusObject != _rootItem) { + //qDebug() << "Swallowed shortcut " << static_cast(event)->key(); + event->accept(); + return true; + } + return false; +} + +/////////////////////////////////////////////////////// +// +// Event handling customization +// + +bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { + // Only intercept events while we're in an active state + if (_paused) { + return false; + } + + +#ifdef DEBUG + // Don't intercept our own events, or we enter an infinite recursion + QObject* recurseTest = originalDestination; + while (recurseTest) { + Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow); + recurseTest = recurseTest->parent(); + } +#endif + + + switch (event->type()) { + case QEvent::Resize: { + QResizeEvent* resizeEvent = static_cast(event); + QGLWidget* widget = dynamic_cast(originalDestination); + if (widget) { + this->resize(resizeEvent->size()); + } + break; + } + + case QEvent::KeyPress: + case QEvent::KeyRelease: { + event->ignore(); + if (QCoreApplication::sendEvent(_quickWindow, event)) { + return event->isAccepted(); + } + break; + } + + case QEvent::Wheel: { + QWheelEvent* wheelEvent = static_cast(event); + QWheelEvent mappedEvent( + mapWindowToUi(wheelEvent->pos(), originalDestination), + wheelEvent->delta(), wheelEvent->buttons(), + wheelEvent->modifiers(), wheelEvent->orientation()); + mappedEvent.ignore(); + if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { + return mappedEvent.isAccepted(); + } + break; + } + + // Fall through + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: { + QMouseEvent* mouseEvent = static_cast(event); + QPointF originalPos = mouseEvent->localPos(); + QPointF transformedPos = _mouseTranslator(originalPos); + transformedPos = mapWindowToUi(transformedPos, originalDestination); + QMouseEvent mappedEvent(mouseEvent->type(), + transformedPos, + mouseEvent->screenPos(), mouseEvent->button(), + mouseEvent->buttons(), mouseEvent->modifiers()); + if (event->type() == QEvent::MouseMove) { + _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos); + } + mappedEvent.ignore(); + if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { + return mappedEvent.isAccepted(); + } + break; + } + + default: + break; + } + + return false; +} + +void OffscreenUi::lockTexture(int texture) { + _fboCache.lockTexture(texture); +} + +void OffscreenUi::releaseTexture(int texture) { + _fboCache.releaseTexture(texture); +} + +void OffscreenUi::pause() { + _paused = true; +} + +void OffscreenUi::resume() { + _paused = false; + requestRender(); +} + +bool OffscreenUi::isPaused() const { + return _paused; +} + +void OffscreenUi::setProxyWindow(QWindow* window) { + _renderControl->_renderWindow = window; +} + +void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { + QQuickItem* item = _rootItem->findChild(name); + // First load? + if (!item) { + load(url, f); + item = _rootItem->findChild(name); + } + if (item) { + item->setEnabled(true); + } +} + +void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function f) { + QQuickItem* item = _rootItem->findChild(name); + // First load? + if (!item) { + load(url, f); + item = _rootItem->findChild(name); + } + if (item) { + item->setEnabled(!item->isEnabled()); + } +} + +void OffscreenUi::messageBox(const QString& title, const QString& text, + ButtonCallback callback, + QMessageBox::Icon icon, + QMessageBox::StandardButtons buttons) { + MessageDialog* pDialog{ nullptr }; + MessageDialog::show([&](QQmlContext* ctx, QObject* item) { + pDialog = item->findChild(); + pDialog->setIcon((MessageDialog::Icon)icon); + pDialog->setTitle(title); + pDialog->setText(text); + pDialog->setStandardButtons(MessageDialog::StandardButtons(static_cast(buttons))); + pDialog->setResultCallback(callback); + }); + pDialog->setEnabled(true); +} + +void OffscreenUi::information(const QString& title, const QString& text, + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, + static_cast(MessageDialog::Information), buttons); +} + +void OffscreenUi::question(const QString& title, const QString& text, + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, + static_cast(MessageDialog::Question), buttons); +} + +void OffscreenUi::warning(const QString& title, const QString& text, + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, + static_cast(MessageDialog::Warning), buttons); +} + +void OffscreenUi::critical(const QString& title, const QString& text, + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + messageBox(title, text, callback, + static_cast(MessageDialog::Critical), buttons); +} + + +OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; + +#include "OffscreenUi.moc" diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h new file mode 100644 index 0000000000..ce40bec943 --- /dev/null +++ b/libraries/ui/src/OffscreenUi.h @@ -0,0 +1,200 @@ +// +// OffscreenUi.h +// interface/src/entities +// +// Created by Bradley Austin Davis on 2015-04-04 +// 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 +// +#pragma once +#ifndef hifi_OffscreenUi_h +#define hifi_OffscreenUi_h + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "OffscreenGlCanvas.h" +#include "FboCache.h" +#include + +#define HIFI_QML_DECL \ +private: \ + static const QString NAME; \ + static const QUrl QML; \ +public: \ + static void registerType(); \ + static void show(std::function f = [](QQmlContext*, QObject*) {}); \ + static void toggle(std::function f = [](QQmlContext*, QObject*) {}); \ + static void load(std::function f = [](QQmlContext*, QObject*) {}); \ +private: + +#define HIFI_QML_DECL_LAMBDA \ +protected: \ + static const QString NAME; \ + static const QUrl QML; \ +public: \ + static void registerType(); \ + static void show(); \ + static void toggle(); \ + static void load(); \ +private: + +#define HIFI_QML_DEF(x) \ + const QUrl x::QML = QUrl(#x ".qml"); \ + const QString x::NAME = #x; \ + \ + void x::registerType() { \ + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ + } \ + \ + void x::show(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->show(QML, NAME, f); \ + } \ + \ + void x::toggle(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->toggle(QML, NAME, f); \ + } \ + void x::load(std::function f) { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->load(QML, f); \ + } + +#define HIFI_QML_DEF_LAMBDA(x, f) \ + const QUrl x::QML = QUrl(#x ".qml"); \ + const QString x::NAME = #x; \ + \ + void x::registerType() { \ + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ + } \ + void x::show() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->show(QML, NAME, f); \ + } \ + void x::toggle() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->toggle(QML, NAME, f); \ + } \ + void x::load() { \ + auto offscreenUi = DependencyManager::get(); \ + offscreenUi->load(QML, f); \ + } + +class OffscreenUi : public OffscreenGlCanvas, public Dependency { + Q_OBJECT + + class QMyQuickRenderControl : public QQuickRenderControl { + protected: + QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{ + if (nullptr == _renderWindow) { + return QQuickRenderControl::renderWindow(offset); + } + if (nullptr != offset) { + offset->rx() = offset->ry() = 0; + } + return _renderWindow; + } + + private: + QWindow* _renderWindow{ nullptr }; + friend class OffscreenUi; + }; + +public: + using MouseTranslator = std::function; + OffscreenUi(); + virtual ~OffscreenUi(); + void create(QOpenGLContext* context); + void resize(const QSize& size); + QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); + QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { + return load(QUrl(qmlSourceFile), f); + } + void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); + void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); + void setBaseUrl(const QUrl& baseUrl); + void addImportPath(const QString& path); + //QQmlContext* getQmlContext(); + QQuickItem* getRootItem(); + void pause(); + void resume(); + bool isPaused() const; + void setProxyWindow(QWindow* window); + bool shouldSwallowShortcut(QEvent* event); + QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); + virtual bool eventFilter(QObject* originalDestination, QEvent* event); + void setMouseTranslator(MouseTranslator mouseTranslator) { + _mouseTranslator = mouseTranslator; + } + + + // Messagebox replacement functions + using ButtonCallback = std::function; + static ButtonCallback NO_OP_CALLBACK; + + static void messageBox(const QString& title, const QString& text, + ButtonCallback f, + QMessageBox::Icon icon, + QMessageBox::StandardButtons buttons); + + static void information(const QString& title, const QString& text, + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::Ok); + + static void question(const QString& title, const QString& text, + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No)); + + static void warning(const QString& title, const QString& text, + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::Ok); + + static void critical(const QString& title, const QString& text, + ButtonCallback callback = NO_OP_CALLBACK, + QMessageBox::StandardButtons buttons = QMessageBox::Ok); + +private: + QObject* finishQmlLoad(std::function f); + +private slots: + void updateQuick(); + +public slots: + void requestUpdate(); + void requestRender(); + void lockTexture(int texture); + void releaseTexture(int texture); + +signals: + void textureUpdated(GLuint texture); + +private: + QMyQuickRenderControl* _renderControl{ new QMyQuickRenderControl }; + QQuickWindow* _quickWindow{ nullptr }; + QQmlEngine* _qmlEngine{ nullptr }; + QQmlComponent* _qmlComponent{ nullptr }; + QQuickItem* _rootItem{ nullptr }; + QTimer _updateTimer; + FboCache _fboCache; + bool _polish{ true }; + bool _paused{ true }; + MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } }; +}; + +#endif diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp new file mode 100644 index 0000000000..5c0f8fb732 --- /dev/null +++ b/libraries/ui/src/VrMenu.cpp @@ -0,0 +1,218 @@ +// +// VrMenu.cpp +// +// Created by Bradley Austin Davis on 2015/04/21 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "VrMenu.h" +#include +#include + +// Binds together a Qt Action or Menu with the QML Menu or MenuItem +// +// TODO On reflection, it may be pointless to use the UUID. Perhaps +// simply creating the bidirectional link pointing to both the widget +// and qml object and inject the pointer into both objects +class MenuUserData : public QObjectUserData { + static const int USER_DATA_ID; + +public: + MenuUserData(QAction* action, QObject* qmlObject) { + init(action, qmlObject); + } + MenuUserData(QMenu* menu, QObject* qmlObject) { + init(menu, qmlObject); + } + + const QUuid uuid{ QUuid::createUuid() }; + + static MenuUserData* forObject(QObject* object) { + return static_cast(object->userData(USER_DATA_ID)); + } + +private: + MenuUserData(const MenuUserData&); + + void init(QObject* widgetObject, QObject* qmlObject) { + widgetObject->setUserData(USER_DATA_ID, this); + qmlObject->setUserData(USER_DATA_ID, this); + qmlObject->setObjectName(uuid.toString()); + // Make sure we can find it again in the future + Q_ASSERT(VrMenu::_instance->findMenuObject(uuid.toString())); + } +}; + +const int MenuUserData::USER_DATA_ID = QObject::registerUserData(); + +HIFI_QML_DEF_LAMBDA(VrMenu, [&](QQmlContext* context, QObject* newItem) { + auto offscreenUi = DependencyManager::get(); + QObject* rootMenu = offscreenUi->getRootItem()->findChild("rootMenu"); + Q_ASSERT(rootMenu); + static_cast(newItem)->setRootMenu(rootMenu); + context->setContextProperty("rootMenu", rootMenu); +}); + +VrMenu* VrMenu::_instance{ nullptr }; +static QQueue> queuedLambdas; + +void VrMenu::executeOrQueue(std::function f) { + if (_instance) { + foreach(std::function priorLambda, queuedLambdas) { + priorLambda(_instance); + } + f(_instance); + } else { + queuedLambdas.push_back(f); + } +} + +void VrMenu::executeQueuedLambdas() { + Q_ASSERT(_instance); + foreach(std::function f, queuedLambdas) { + f(_instance); + } + queuedLambdas.clear(); +} + +VrMenu::VrMenu(QQuickItem* parent) : QQuickItem(parent) { + _instance = this; + this->setEnabled(false); +} + + +// QML helper functions +QObject* addMenu(QObject* parent, const QString& text) { + // FIXME add more checking here to ensure no name conflicts + QVariant returnedValue; + QMetaObject::invokeMethod(parent, "addMenu", Qt::DirectConnection, + Q_RETURN_ARG(QVariant, returnedValue), + Q_ARG(QVariant, text)); + QObject* result = returnedValue.value(); + if (result) { + result->setObjectName(text); + } + return result; +} + +class QQuickMenuItem; +QObject* addItem(QObject* parent, const QString& text) { + // FIXME add more checking here to ensure no name conflicts + QQuickMenuItem* returnedValue{ nullptr }; + bool invokeResult = QMetaObject::invokeMethod(parent, "addItem", Qt::DirectConnection, + Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_ARG(QString, text)); + Q_ASSERT(invokeResult); + QObject* result = reinterpret_cast(returnedValue); + return result; +} + +const QObject* VrMenu::findMenuObject(const QString& menuOption) const { + if (menuOption.isEmpty()) { + return _rootMenu; + } + const QObject* result = _rootMenu->findChild(menuOption); + return result; +} + +QObject* VrMenu::findMenuObject(const QString& menuOption) { + if (menuOption.isEmpty()) { + return _rootMenu; + } + QObject* result = _rootMenu->findChild(menuOption); + return result; +} + +void VrMenu::setRootMenu(QObject* rootMenu) { + _rootMenu = rootMenu; +} + +void VrMenu::addMenu(QMenu* menu) { + Q_ASSERT(!MenuUserData::forObject(menu)); + QObject * parent = menu->parent(); + QObject * qmlParent; + if (dynamic_cast(parent)) { + MenuUserData* userData = MenuUserData::forObject(parent); + qmlParent = findMenuObject(userData->uuid.toString()); + } else if (dynamic_cast(parent)) { + qmlParent = _rootMenu; + } else { + Q_ASSERT(false); + } + QObject* result = ::addMenu(qmlParent, menu->title()); + new MenuUserData(menu, result); +} + +void updateQmlItemFromAction(QObject* target, QAction* source) { + target->setProperty("checkable", source->isCheckable()); + target->setProperty("enabled", source->isEnabled()); + target->setProperty("visible", source->isVisible()); + target->setProperty("text", source->text()); + target->setProperty("checked", source->isChecked()); +} + +void bindActionToQmlAction(QObject* qmlAction, QAction* action) { + new MenuUserData(action, qmlAction); + updateQmlItemFromAction(qmlAction, action); + QObject::connect(action, &QAction::changed, [=] { + updateQmlItemFromAction(qmlAction, action); + }); + QObject::connect(action, &QAction::toggled, [=](bool checked) { + qmlAction->setProperty("checked", checked); + }); + QObject::connect(qmlAction, SIGNAL(triggered()), action, SLOT(trigger())); +} + +void VrMenu::addAction(QMenu* menu, QAction* action) { + Q_ASSERT(!MenuUserData::forObject(action)); + Q_ASSERT(MenuUserData::forObject(menu)); + MenuUserData* userData = MenuUserData::forObject(menu); + QObject* parent = findMenuObject(userData->uuid.toString()); + Q_ASSERT(parent); + QObject* result = ::addItem(parent, action->text()); + Q_ASSERT(result); + // Bind the QML and Widget together + bindActionToQmlAction(result, action); +} + +void VrMenu::insertAction(QAction* before, QAction* action) { + QObject* beforeQml{ nullptr }; + { + MenuUserData* beforeUserData = MenuUserData::forObject(before); + Q_ASSERT(beforeUserData); + beforeQml = findMenuObject(beforeUserData->uuid.toString()); + } + + QObject* menu = beforeQml->parent(); + int index{ -1 }; + QVariant itemsVar = menu->property("items"); + QList items = itemsVar.toList(); + // FIXME add more checking here to ensure no name conflicts + for (index = 0; index < items.length(); ++index) { + QObject* currentQmlItem = items.at(index).value(); + if (currentQmlItem == beforeQml) { + break; + } + } + + QObject* result{ nullptr }; + if (index < 0 || index >= items.length()) { + result = ::addItem(menu, action->text()); + } else { + QQuickMenuItem* returnedValue{ nullptr }; + bool invokeResult = QMetaObject::invokeMethod(menu, "insertItem", Qt::DirectConnection, + Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_ARG(int, index), Q_ARG(QString, action->text())); + Q_ASSERT(invokeResult); + result = reinterpret_cast(returnedValue); + } + Q_ASSERT(result); + bindActionToQmlAction(result, action); +} + +void VrMenu::removeAction(QAction* action) { + // FIXME implement +} diff --git a/libraries/ui/src/VrMenu.h b/libraries/ui/src/VrMenu.h new file mode 100644 index 0000000000..06a16588af --- /dev/null +++ b/libraries/ui/src/VrMenu.h @@ -0,0 +1,49 @@ +// +// VrMenu.h +// +// Created by Bradley Austin Davis on 2015/04/21 +// 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 +// + +#pragma once +#ifndef hifi_VrMenu_h +#define hifi_VrMenu_h + +#include +#include +#include +#include +#include +#include +#include "OffscreenUi.h" + +// FIXME break up the rendering code (VrMenu) and the code for mirroring a Widget based menu in QML +class VrMenu : public QQuickItem { + Q_OBJECT + HIFI_QML_DECL_LAMBDA + +public: + static void executeOrQueue(std::function f); + static void executeQueuedLambdas(); + VrMenu(QQuickItem* parent = nullptr); + void addMenu(QMenu* menu); + void addAction(QMenu* parent, QAction* action); + void insertAction(QAction* before, QAction* action); + void removeAction(QAction* action); + + void setRootMenu(QObject* rootMenu); + +protected: + QObject* _rootMenu{ nullptr }; + + QObject* findMenuObject(const QString& name); + const QObject* findMenuObject(const QString& name) const; + + static VrMenu* _instance; + friend class MenuUserData; +}; + +#endif // hifi_VrMenu_h diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp index 705a50aa10..4cfadbccfc 100644 --- a/tests/octree/src/OctreeTests.cpp +++ b/tests/octree/src/OctreeTests.cpp @@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests(bool verbose) { props.setHasProperty(PROP_POSITION); props.setHasProperty(PROP_RADIUS); props.setHasProperty(PROP_MODEL_URL); - props.setHasProperty(PROP_COLLISION_MODEL_URL); + props.setHasProperty(PROP_COMPOUND_SHAPE_URL); props.setHasProperty(PROP_ROTATION); QByteArray encoded = props.encode(); diff --git a/tests/physics/src/ShapeInfoTests.cpp b/tests/physics/src/ShapeInfoTests.cpp index bf2a98eb10..ef5bd0be39 100644 --- a/tests/physics/src/ShapeInfoTests.cpp +++ b/tests/physics/src/ShapeInfoTests.cpp @@ -147,9 +147,7 @@ void ShapeInfoTests::testBoxShape() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: NULL Box shape" << std::endl; } - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; DoubleHashKey otherKey = otherInfo.getHash(); if (key.getHash() != otherKey.getHash()) { std::cout << __FILE__ << ":" << __LINE__ @@ -172,9 +170,7 @@ void ShapeInfoTests::testSphereShape() { btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; DoubleHashKey otherKey = otherInfo.getHash(); if (key.getHash() != otherKey.getHash()) { std::cout << __FILE__ << ":" << __LINE__ @@ -198,9 +194,7 @@ void ShapeInfoTests::testCylinderShape() { btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; DoubleHashKey otherKey = otherInfo.getHash(); if (key.getHash() != otherKey.getHash()) { std::cout << __FILE__ << ":" << __LINE__ @@ -225,9 +219,7 @@ void ShapeInfoTests::testCapsuleShape() { btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; DoubleHashKey otherKey = otherInfo.getHash(); if (key.getHash() != otherKey.getHash()) { std::cout << __FILE__ << ":" << __LINE__ diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp index d86f296d0e..d2b4ca70fa 100644 --- a/tests/physics/src/ShapeManagerTests.cpp +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -187,9 +187,7 @@ void ShapeManagerTests::addBoxShape() { ShapeManager shapeManager; btCollisionShape* shape = shapeManager.getShape(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; btCollisionShape* otherShape = shapeManager.getShape(otherInfo); if (shape != otherShape) { std::cout << __FILE__ << ":" << __LINE__ @@ -205,9 +203,7 @@ void ShapeManagerTests::addSphereShape() { ShapeManager shapeManager; btCollisionShape* shape = shapeManager.getShape(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; btCollisionShape* otherShape = shapeManager.getShape(otherInfo); if (shape != otherShape) { std::cout << __FILE__ << ":" << __LINE__ @@ -225,9 +221,7 @@ void ShapeManagerTests::addCylinderShape() { ShapeManager shapeManager; btCollisionShape* shape = shapeManager.getShape(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; btCollisionShape* otherShape = shapeManager.getShape(otherInfo); if (shape != otherShape) { std::cout << __FILE__ << ":" << __LINE__ @@ -246,9 +240,7 @@ void ShapeManagerTests::addCapsuleShape() { ShapeManager shapeManager; btCollisionShape* shape = shapeManager.getShape(info); - ShapeInfo otherInfo; - ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); - + ShapeInfo otherInfo = info; btCollisionShape* otherShape = shapeManager.getShape(otherInfo); if (shape != otherShape) { std::cout << __FILE__ << ":" << __LINE__ diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index b62ab68c22..0ba7416b28 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -21,13 +21,17 @@ #include #include #include +#include #include #include #include +#include + #include #include #include #include +#include class RateCounter { std::vector times; @@ -65,115 +69,143 @@ public: } }; + +const QString& getQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/"; + qDebug() << "Qml Path: " << dir; + } + return dir; +} + // Create a simple OpenGL window that renders text in various ways -class QTestWindow: public QWindow { +class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext * _context; + + QOpenGLContext* _context{ nullptr }; QSize _size; TextRenderer* _textRenderer[4]; RateCounter fps; protected: - void resizeEvent(QResizeEvent * ev) override { - QWindow::resizeEvent(ev); - _size = ev->size(); - resizeGl(); - } + void renderText(); - void resizeGl() { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, _size.width(), _size.height(), 0, 1, -1); - glMatrixMode(GL_MODELVIEW); - glViewport(0, 0, _size.width(), _size.height()); +private: + void resizeWindow(const QSize& size) { + _size = size; } public: - QTestWindow(); - virtual ~QTestWindow() { + QTestWindow() { + setSurfaceType(QSurface::OpenGLSurface); + QSurfaceFormat format; + // Qt Quick may need a depth and stencil buffer. Always make sure these are available. + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setVersion(4, 5); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setOption(QSurfaceFormat::DebugContext); + + setFormat(format); + + _context = new QOpenGLContext; + _context->setFormat(format); + _context->create(); + + show(); + makeCurrent(); + + { + QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); + // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } + qDebug() << (const char*)glGetString(GL_VERSION); + +#ifdef WIN32 + glewExperimental = true; + GLenum err = glewInit(); + if (GLEW_OK != err) { + /* Problem: glewInit failed, something is seriously wrong. */ + const GLubyte * errStr = glewGetErrorString(err); + qDebug("Error: %s\n", errStr); + } + qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); + + if (wglewGetExtension("WGL_EXT_swap_control")) { + int swapInterval = wglGetSwapIntervalEXT(); + qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + } + glGetError(); +#endif + + _textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); + _textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, + TextRenderer::SHADOW_EFFECT); + _textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, + false, TextRenderer::OUTLINE_EFFECT); + _textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.2f, 0.2f, 0.2f, 1); + glDisable(GL_DEPTH_TEST); + + makeCurrent(); + + setFramePosition(QPoint(-1000, 0)); + resize(QSize(800, 600)); } + + virtual ~QTestWindow() { + } + + void draw(); void makeCurrent() { _context->makeCurrent(this); } - void draw(); +protected: + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } }; #ifndef SERIF_FONT_FAMILY #define SERIF_FONT_FAMILY "Times New Roman" #endif -QTestWindow::QTestWindow() { - setSurfaceType(QSurface::OpenGLSurface); - - QSurfaceFormat format; - // Qt Quick may need a depth and stencil buffer. Always make sure these are available. - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - format.setVersion(3, 2); - format.setProfile( - QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); - setFormat(format); - - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); - - show(); - makeCurrent(); - qDebug() << (const char*) glGetString(GL_VERSION); - -#ifdef WIN32 - glewExperimental = true; - GLenum err = glewInit(); - if (GLEW_OK != err) { - /* Problem: glewInit failed, something is seriously wrong. */ - const GLubyte * errStr = glewGetErrorString(err); - qDebug("Error: %s\n", errStr); - } - qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); - - if (wglewGetExtension("WGL_EXT_swap_control")) { - int swapInterval = wglGetSwapIntervalEXT(); - qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } - glGetError(); -#endif - - setFramePosition(QPoint(100, -900)); - resize(QSize(800, 600)); - _size = QSize(800, 600); - - _textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); - _textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, - TextRenderer::SHADOW_EFFECT); - _textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, - false, TextRenderer::OUTLINE_EFFECT); - _textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glClearColor(0.2f, 0.2f, 0.2f, 1); - glDisable(GL_DEPTH_TEST); - resizeGl(); -} - -static const wchar_t * EXAMPLE_TEXT = L"Hello"; -//static const wchar_t * EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; +static const wchar_t* EXAMPLE_TEXT = L"Hello"; +//static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; static const glm::uvec2 QUAD_OFFSET(10, 10); static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { 1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } }; -void QTestWindow::draw() { - makeCurrent(); - glClear(GL_COLOR_BUFFER_BIT); +void QTestWindow::renderText() { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, _size.width(), _size.height(), 0, 1, -1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); const glm::uvec2 size = glm::uvec2(_size.width() / 2, _size.height() / 2); - const glm::uvec2 offsets[4] = { { QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x - + QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x + QUAD_OFFSET.x, size.y - + QUAD_OFFSET.y }, { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, }; + + const glm::uvec2 offsets[4] = { + { QUAD_OFFSET.x, QUAD_OFFSET.y }, + { size.x + QUAD_OFFSET.x, QUAD_OFFSET.y }, + { size.x + QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, + { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, + }; QString str = QString::fromWCharArray(EXAMPLE_TEXT); for (int i = 0; i < 4; ++i) { @@ -200,8 +232,22 @@ void QTestWindow::draw() { glm::vec4(COLORS[i], 1.0f)); } } +} + +void QTestWindow::draw() { + if (!isVisible()) { + return; + } + + makeCurrent(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); + + renderText(); + _context->swapBuffers(this); glFinish(); + fps.increment(); if (fps.elapsed() >= 2.0f) { qDebug() << "FPS: " << fps.rate(); @@ -210,7 +256,7 @@ void QTestWindow::draw() { } int main(int argc, char** argv) { - QApplication app(argc, argv); + QGuiApplication app(argc, argv); QTestWindow window; QTimer timer; timer.setInterval(1); diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt new file mode 100644 index 0000000000..3ff8555fa2 --- /dev/null +++ b/tests/ui/CMakeLists.txt @@ -0,0 +1,15 @@ +set(TARGET_NAME ui-tests) + +setup_hifi_project(Widgets OpenGL Network Qml Quick Script) + +if (WIN32) + add_dependency_external_projects(glew) + find_package(GLEW REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${GLEW_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} wsock32.lib opengl32.lib Winmm.lib) +endif() + +# link in the shared libraries +link_hifi_libraries(ui render-utils gpu shared) + +copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/ui/main.qml b/tests/ui/main.qml new file mode 100644 index 0000000000..168b9fb291 --- /dev/null +++ b/tests/ui/main.qml @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.2 +import "qml/UI.js" as UI +import "qml" +//import "/Users/bdavis/Git/hifi/interface/resources/qml" + +Item { + anchors.fill: parent + visible: true + //title: "Qt Quick Controls Gallery" + + MessageDialog { + id: aboutDialog + icon: StandardIcon.Information + title: "About" + text: "Qt Quick Controls Gallery" + informativeText: "This example demonstrates most of the available Qt Quick Controls." + } + + Action { + id: copyAction + text: "&Copy" + shortcut: StandardKey.Copy + iconName: "edit-copy" + enabled: (!!activeFocusItem && !!activeFocusItem["copy"]) + onTriggered: activeFocusItem.copy() + } + + Action { + id: cutAction + text: "Cu&t" + shortcut: StandardKey.Cut + iconName: "edit-cut" + enabled: (!!activeFocusItem && !!activeFocusItem["cut"]) + onTriggered: activeFocusItem.cut() + } + + Action { + id: pasteAction + text: "&Paste" + shortcut: StandardKey.Paste + iconName: "edit-paste" + enabled: (!!activeFocusItem && !!activeFocusItem["paste"]) + onTriggered: activeFocusItem.paste() + } + +// toolBar: ToolBar { +// RowLayout { +// anchors.fill: parent +// anchors.margins: spacing +// Label { +// text: UI.label +// } +// Item { Layout.fillWidth: true } +// CheckBox { +// id: enabler +// text: "Enabled" +// checked: true +// } +// } +// } + +// menuBar: MenuBar { +// Menu { +// title: "&File" +// MenuItem { +// text: "E&xit" +// shortcut: StandardKey.Quit +// onTriggered: Qt.quit() +// } +// } +// Menu { +// title: "&Edit" +// visible: tabView.currentIndex == 2 +// MenuItem { action: cutAction } +// MenuItem { action: copyAction } +// MenuItem { action: pasteAction } +// } +// Menu { +// title: "&Help" +// MenuItem { +// text: "About..." +// onTriggered: aboutDialog.open() +// } +// } +// } + + TabView { + id: tabView + + anchors.fill: parent + anchors.margins: UI.margin + tabPosition: UI.tabPosition + + Layout.minimumWidth: 360 + Layout.minimumHeight: 360 + Layout.preferredWidth: 480 + Layout.preferredHeight: 640 + + Tab { + title: "Buttons" + ButtonPage { + enabled: enabler.checked + } + } + Tab { + title: "Progress" + ProgressPage { + enabled: enabler.checked + } + } + Tab { + title: "Input" + InputPage { + enabled: enabler.checked + } + } + } +} diff --git a/tests/ui/qml/ButtonPage.qml b/tests/ui/qml/ButtonPage.qml new file mode 100644 index 0000000000..0ed7e2d6ad --- /dev/null +++ b/tests/ui/qml/ButtonPage.qml @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing) + height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing) + + GridLayout { + id: grid + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: grid.rowSpacing + anchors.rightMargin: grid.rowSpacing + anchors.topMargin: grid.columnSpacing + + columns: page.width < page.height ? 1 : 2 + + GroupBox { + title: "Button" + Layout.fillWidth: true + Layout.columnSpan: grid.columns + RowLayout { + anchors.fill: parent + Button { text: "OK"; isDefault: true } + Button { text: "Cancel" } + Item { Layout.fillWidth: true } + Button { + text: "Attach" + menu: Menu { + MenuItem { text: "Image" } + MenuItem { text: "Document" } + } + } + } + } + + GroupBox { + title: "CheckBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + CheckBox { text: "E-mail"; checked: true } + CheckBox { text: "Calendar"; checked: true } + CheckBox { text: "Contacts" } + } + } + + GroupBox { + title: "RadioButton" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ExclusiveGroup { id: radioGroup } + RadioButton { text: "Portrait"; exclusiveGroup: radioGroup } + RadioButton { text: "Landscape"; exclusiveGroup: radioGroup } + RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true } + } + } + + GroupBox { + title: "Switch" + Layout.fillWidth: true + Layout.columnSpan: grid.columns + ColumnLayout { + anchors.fill: parent + RowLayout { + Label { text: "Wi-Fi"; Layout.fillWidth: true } + Switch { checked: true } + } + RowLayout { + Label { text: "Bluetooth"; Layout.fillWidth: true } + Switch { checked: false } + } + } + } + } + } +} diff --git a/tests/ui/qml/InputPage.qml b/tests/ui/qml/InputPage.qml new file mode 100644 index 0000000000..cb1878d023 --- /dev/null +++ b/tests/ui/qml/InputPage.qml @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) + height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) + + ColumnLayout { + id: column + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: column.spacing + + GroupBox { + title: "TextField" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 } + TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true } + } + } + + GroupBox { + title: "ComboBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ComboBox { + model: Qt.fontFamilies() + Layout.fillWidth: true + } + ComboBox { + editable: true + model: ListModel { + id: listModel + ListElement { text: "Apple" } + ListElement { text: "Banana" } + ListElement { text: "Coconut" } + ListElement { text: "Orange" } + } + onAccepted: { + if (find(currentText) === -1) { + listModel.append({text: editText}) + currentIndex = find(editText) + } + } + Layout.fillWidth: true + } + } + } + + GroupBox { + title: "SpinBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + SpinBox { value: 99; Layout.fillWidth: true; z: 1 } + SpinBox { decimals: 2; Layout.fillWidth: true } + } + } + } + } +} diff --git a/tests/ui/qml/ProgressPage.qml b/tests/ui/qml/ProgressPage.qml new file mode 100644 index 0000000000..a1fa596f79 --- /dev/null +++ b/tests/ui/qml/ProgressPage.qml @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) + height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) + + ColumnLayout { + id: column + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: column.spacing + + GroupBox { + title: "ProgressBar" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ProgressBar { indeterminate: true; Layout.fillWidth: true } + ProgressBar { value: slider.value; Layout.fillWidth: true } + } + } + + GroupBox { + title: "Slider" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + Slider { id: slider; value: 0.5; Layout.fillWidth: true } + } + } + + GroupBox { + title: "BusyIndicator" + Layout.fillWidth: true + BusyIndicator { running: true } + } + } + } +} diff --git a/tests/ui/qml/UI.js b/tests/ui/qml/UI.js new file mode 100644 index 0000000000..0286ac56a6 --- /dev/null +++ b/tests/ui/qml/UI.js @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +.pragma library + +var margin = 2 +var tabPosition = Qt.TopEdge +var label = "" diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp new file mode 100644 index 0000000000..a5bc50b288 --- /dev/null +++ b/tests/ui/src/main.cpp @@ -0,0 +1,493 @@ +// +// main.cpp +// tests/render-utils/src +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OffscreenUi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "MessageDialog.h" +#include "VrMenu.h" + +class RateCounter { + std::vector times; + QElapsedTimer timer; +public: + RateCounter() { + timer.start(); + } + + void reset() { + times.clear(); + } + + unsigned int count() const { + return times.size() - 1; + } + + float elapsed() const { + if (times.size() < 1) { + return 0.0f; + } + float elapsed = *times.rbegin() - *times.begin(); + return elapsed; + } + + void increment() { + times.push_back(timer.elapsed() / 1000.0f); + } + + float rate() const { + if (elapsed() == 0.0f) { + return NAN; + } + return (float) count() / elapsed(); + } +}; + + +class MenuConstants : public QObject{ + Q_OBJECT + Q_ENUMS(Item) + +public: + enum Item { + AboutApp, + AddRemoveFriends, + AddressBar, + AlignForearmsWithWrists, + AlternateIK, + AmbientOcclusion, + Animations, + Atmosphere, + Attachments, + AudioNoiseReduction, + AudioScope, + AudioScopeFiftyFrames, + AudioScopeFiveFrames, + AudioScopeFrames, + AudioScopePause, + AudioScopeTwentyFrames, + AudioStats, + AudioStatsShowInjectedStreams, + BandwidthDetails, + BlueSpeechSphere, + BookmarkLocation, + Bookmarks, + CascadedShadows, + CachesSize, + Chat, + Collisions, + Console, + ControlWithSpeech, + CopyAddress, + CopyPath, + DecreaseAvatarSize, + DeleteBookmark, + DisableActivityLogger, + DisableLightEntities, + DisableNackPackets, + DiskCacheEditor, + DisplayHands, + DisplayHandTargets, + DisplayModelBounds, + DisplayModelTriangles, + DisplayModelElementChildProxies, + DisplayModelElementProxy, + DisplayDebugTimingDetails, + DontDoPrecisionPicking, + DontFadeOnOctreeServerChanges, + DontRenderEntitiesAsScene, + EchoLocalAudio, + EchoServerAudio, + EditEntitiesHelp, + Enable3DTVMode, + EnableCharacterController, + EnableGlowEffect, + EnableVRMode, + ExpandMyAvatarSimulateTiming, + ExpandMyAvatarTiming, + ExpandOtherAvatarTiming, + ExpandPaintGLTiming, + ExpandUpdateTiming, + Faceshift, + FilterSixense, + FirstPerson, + FrameTimer, + Fullscreen, + FullscreenMirror, + GlowWhenSpeaking, + NamesAboveHeads, + GoToUser, + HMDTools, + IncreaseAvatarSize, + KeyboardMotorControl, + LeapMotionOnHMD, + LoadScript, + LoadScriptURL, + LoadRSSDKFile, + LodTools, + Login, + Log, + LowVelocityFilter, + Mirror, + MuteAudio, + MuteEnvironment, + NoFaceTracking, + NoShadows, + OctreeStats, + OffAxisProjection, + OnlyDisplayTopTen, + PackageModel, + Pair, + PipelineWarnings, + Preferences, + Quit, + ReloadAllScripts, + RenderBoundingCollisionShapes, + RenderFocusIndicator, + RenderHeadCollisionShapes, + RenderLookAtVectors, + RenderSkeletonCollisionShapes, + RenderTargetFramerate, + RenderTargetFramerateUnlimited, + RenderTargetFramerate60, + RenderTargetFramerate50, + RenderTargetFramerate40, + RenderTargetFramerate30, + RenderTargetFramerateVSyncOn, + RenderResolution, + RenderResolutionOne, + RenderResolutionTwoThird, + RenderResolutionHalf, + RenderResolutionThird, + RenderResolutionQuarter, + RenderAmbientLight, + RenderAmbientLightGlobal, + RenderAmbientLight0, + RenderAmbientLight1, + RenderAmbientLight2, + RenderAmbientLight3, + RenderAmbientLight4, + RenderAmbientLight5, + RenderAmbientLight6, + RenderAmbientLight7, + RenderAmbientLight8, + RenderAmbientLight9, + ResetAvatarSize, + ResetSensors, + RunningScripts, + RunTimingTests, + ScriptEditor, + ScriptedMotorControl, + ShowBordersEntityNodes, + ShowIKConstraints, + SimpleShadows, + SixenseEnabled, + SixenseMouseInput, + SixenseLasers, + ShiftHipsForIdleAnimations, + Stars, + Stats, + StereoAudio, + StopAllScripts, + SuppressShortTimings, + TestPing, + ToolWindow, + TransmitterDrive, + TurnWithHead, + UseAudioForMouth, + UseCamera, + VelocityFilter, + VisibleToEveryone, + VisibleToFriends, + VisibleToNoOne, + Wireframe, + }; + +public: + MenuConstants(QObject* parent = nullptr) : QObject(parent) { + + } +}; + +const QString& getQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/"; + qDebug() << "Qml Path: " << dir; + } + return dir; +} + +const QString& getTestQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Qml Test Path: " << dir; + } + return dir; +} + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow, private QOpenGLFunctions { + Q_OBJECT + + QOpenGLContext* _context{ nullptr }; + QSize _size; + bool _altPressed{ false }; + RateCounter fps; + QTimer _timer; + int testQmlTexture{ 0 }; + +public: + QObject* rootMenu; + + QTestWindow() { + _timer.setInterval(1); + connect(&_timer, &QTimer::timeout, [=] { + draw(); + }); + + DependencyManager::set(); + setSurfaceType(QSurface::OpenGLSurface); + + QSurfaceFormat format; + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + format.setVersion(4, 1); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setOption(QSurfaceFormat::DebugContext); + + setFormat(format); + + _context = new QOpenGLContext; + _context->setFormat(format); + if (!_context->create()) { + qFatal("Could not create OpenGL context"); + } + + show(); + makeCurrent(); + initializeOpenGLFunctions(); + + { + QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); + // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } + + qDebug() << (const char*)this->glGetString(GL_VERSION); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.2f, 0.2f, 0.2f, 1); + glDisable(GL_DEPTH_TEST); + + MessageDialog::registerType(); + VrMenu::registerType(); + qmlRegisterType("Hifi", 1, 0, "MenuConstants"); + + + auto offscreenUi = DependencyManager::get(); + offscreenUi->create(_context); + connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) { + offscreenUi->lockTexture(textureId); + assert(!glGetError()); + GLuint oldTexture = testQmlTexture; + testQmlTexture = textureId; + if (oldTexture) { + offscreenUi->releaseTexture(oldTexture); + } + }); + + makeCurrent(); + + offscreenUi->setProxyWindow(this); + setFramePosition(QPoint(-1000, 0)); + resize(QSize(800, 600)); + +#ifdef QML_CONTROL_GALLERY + offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir())); + offscreenUi->load(QUrl("main.qml")); +#else + offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir())); + offscreenUi->load(QUrl("TestRoot.qml")); + offscreenUi->load(QUrl("TestMenu.qml")); + // Requires a root menu to have been loaded before it can load + VrMenu::load(); +#endif + installEventFilter(offscreenUi.data()); + offscreenUi->resume(); + _timer.start(); + } + + virtual ~QTestWindow() { + DependencyManager::destroy(); + } + +private: + void draw() { + if (!isVisible()) { + return; + } + + makeCurrent(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); + + renderQml(); + + _context->swapBuffers(this); + glFinish(); + + fps.increment(); + if (fps.elapsed() >= 2.0f) { + qDebug() << "FPS: " << fps.rate(); + fps.reset(); + } + } + + void makeCurrent() { + _context->makeCurrent(this); + } + + void renderQml(); + + void resizeWindow(const QSize & size) { + _size = size; + DependencyManager::get()->resize(_size); + } + + +protected: + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } + + + void keyPressEvent(QKeyEvent* event) { + _altPressed = Qt::Key_Alt == event->key(); + switch (event->key()) { + case Qt::Key_L: + if (event->modifiers() & Qt::CTRL) { + } + break; + case Qt::Key_K: + if (event->modifiers() & Qt::CTRL) { + OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){ + qDebug() << b; + }); + } + break; + case Qt::Key_J: + if (event->modifiers() & Qt::CTRL) { + auto offscreenUi = DependencyManager::get(); + rootMenu = offscreenUi->getRootItem()->findChild("rootMenu"); + QMetaObject::invokeMethod(rootMenu, "popup"); + } + break; + } + QWindow::keyPressEvent(event); + } + QQmlContext* menuContext{ nullptr }; + void keyReleaseEvent(QKeyEvent *event) { + if (_altPressed && Qt::Key_Alt == event->key()) { + VrMenu::toggle(); + } + } + + void moveEvent(QMoveEvent* event) { + static qreal oldPixelRatio = 0.0; + if (devicePixelRatio() != oldPixelRatio) { + oldPixelRatio = devicePixelRatio(); + resizeWindow(size()); + } + QWindow::moveEvent(event); + } +}; + +void QTestWindow::renderQml() { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + if (testQmlTexture > 0) { + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, testQmlTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + glBegin(GL_QUADS); + { + glTexCoord2f(0, 0); + glVertex2f(-1, -1); + glTexCoord2f(0, 1); + glVertex2f(-1, 1); + glTexCoord2f(1, 1); + glVertex2f(1, 1); + glTexCoord2f(1, 0); + glVertex2f(1, -1); + } + glEnd(); +} + + +const char * LOG_FILTER_RULES = R"V0G0N( +hifi.offscreen.focus.debug=false +qt.quick.mouse.debug=false +)V0G0N"; + +//int main(int argc, char *argv[]) { +// QGuiApplication app(argc, argv); +// QQmlApplicationEngine engine; +// engine.setBaseUrl(QUrl::fromLocalFile(getQmlDir())); +// engine.load(QUrl("Main.qml")); +// return app.exec(); +//} + +int main(int argc, char** argv) { + QGuiApplication app(argc, argv); + QLoggingCategory::setFilterRules(LOG_FILTER_RULES); + QTestWindow window; + app.exec(); + return 0; +} + +#include "main.moc" diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ba2938aaa6..55994f3d89 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,12 +1,10 @@ # add the tool directories add_subdirectory(mtc) set_target_properties(mtc PROPERTIES FOLDER "Tools") + add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") +add_subdirectory(vhacd-util) +set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") -find_package(VHACD) -if(VHACD_FOUND) -add_subdirectory(vhacd) -# set_target_properties(vhacd PROPERTIES FOLDER "Tools") -endif() diff --git a/tools/vhacd-util/CMakeLists.txt b/tools/vhacd-util/CMakeLists.txt new file mode 100644 index 0000000000..c94b2ad083 --- /dev/null +++ b/tools/vhacd-util/CMakeLists.txt @@ -0,0 +1,18 @@ +set(TARGET_NAME vhacd-util) +setup_hifi_project(Core Widgets) +link_hifi_libraries(shared fbx model gpu) + +add_dependency_external_projects(vhacd) + +find_package(VHACD REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${VHACD_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES}) + +if (UNIX AND NOT APPLE) + include(FindOpenMP) + if(OPENMP_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") + endif() +endif () diff --git a/tools/vhacd/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp similarity index 100% rename from tools/vhacd/src/VHACDUtil.cpp rename to tools/vhacd-util/src/VHACDUtil.cpp diff --git a/tools/vhacd/src/VHACDUtil.h b/tools/vhacd-util/src/VHACDUtil.h similarity index 100% rename from tools/vhacd/src/VHACDUtil.h rename to tools/vhacd-util/src/VHACDUtil.h diff --git a/tools/vhacd/src/VHACDUtilApp.cpp b/tools/vhacd-util/src/VHACDUtilApp.cpp similarity index 100% rename from tools/vhacd/src/VHACDUtilApp.cpp rename to tools/vhacd-util/src/VHACDUtilApp.cpp diff --git a/tools/vhacd/src/VHACDUtilApp.h b/tools/vhacd-util/src/VHACDUtilApp.h similarity index 100% rename from tools/vhacd/src/VHACDUtilApp.h rename to tools/vhacd-util/src/VHACDUtilApp.h diff --git a/tools/vhacd/src/main.cpp b/tools/vhacd-util/src/main.cpp similarity index 100% rename from tools/vhacd/src/main.cpp rename to tools/vhacd-util/src/main.cpp diff --git a/tools/vhacd/CMakeLists.txt b/tools/vhacd/CMakeLists.txt deleted file mode 100644 index f003b685e0..0000000000 --- a/tools/vhacd/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -set(TARGET_NAME vhacd-util) -setup_hifi_project(Core Widgets) -link_hifi_libraries(shared model fbx gpu networking octree) - -#find_package(VHACD REQUIRED) done in CMakeList.txt in parent directory -target_include_directories(${TARGET_NAME} PUBLIC ${VHACD_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES}) - -if(NOT WIN32) - find_package( Threads) - target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) - - include(FindOpenMP) - if(OPENMP_FOUND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") - endif() -endif() - -add_dependency_external_projects(glm) -find_package(GLM REQUIRED) -target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})