//// Application.cpp// interface//// Created by Andrzej Kapolka on 5/10/13.// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #ifdef WIN32#include <Systime.h>#endif #include <sstream> #include <stdlib.h>#include <cmath>#include <math.h> #include <glm/gtx/component_wise.hpp>#include <glm/gtx/quaternion.hpp>#include <glm/gtx/vector_angle.hpp> // include this before QGLWidget, which includes an earlier version of OpenGL#include "InterfaceConfig.h" #include <QActionGroup>#include <QColorDialog>#include <QDesktopWidget>#include <QCheckBox>#include <QImage>#include <QKeyEvent>#include <QMainWindow>#include <QMenuBar>#include <QMouseEvent>#include <QNetworkAccessManager>#include <QNetworkReply>#include <QNetworkDiskCache>#include <QOpenGLFramebufferObject>#include <QObject>#include <QWheelEvent>#include <QSettings>#include <QShortcut>#include <QTimer>#include <QUrl>#include <QtDebug>#include <QFileDialog>#include <QDesktopServices>#include <QXmlStreamReader>#include <QXmlStreamAttributes>#include <QMediaPlayer>#include <QMimeData> #include <QMessageBox> #include <AccountManager.h>#include <AudioInjector.h>#include <Logging.h>#include <OctalCode.h>#include <PacketHeaders.h>#include <ParticlesScriptingInterface.h>#include <PerfStat.h>#include <UUID.h>#include <VoxelSceneStats.h> #include "Application.h"#include "ClipboardScriptingInterface.h"#include "InterfaceVersion.h"#include "Menu.h"#include "MenuScriptingInterface.h"#include "Util.h"#include "devices/OculusManager.h"#include "devices/TV3DManager.h"#include "renderer/ProgramObject.h"#include "ui/TextRenderer.h"#include "InfoView.h"#include "ui/Snapshot.h" using namespace std; // Starfield informationstatic unsigned STARFIELD_NUM_STARS = 50000;static unsigned STARFIELD_SEED = 1; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff // in the idle loop? (60 FPS is default)static QTimer* idleTimer = NULL; const int STARTUP_JITTER_SAMPLES = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / 2; // Startup optimistically with small jitter buffer that // will start playback on the second received audio packet. const int MIRROR_VIEW_TOP_PADDING = 5;const int MIRROR_VIEW_LEFT_PADDING = 10;const int MIRROR_VIEW_WIDTH = 265;const int MIRROR_VIEW_HEIGHT = 215;const float MIRROR_FULLSCREEN_DISTANCE = 0.35f;const float MIRROR_REARVIEW_DISTANCE = 0.65f;const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f;const float MIRROR_FIELD_OF_VIEW = 30.0f; const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml";const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; const int STATS_PELS_PER_LINE = 20; const QString CUSTOM_URL_SCHEME = "hifi:"; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.size() > 0) { QString messageWithNewLine = message + "\n"; fprintf(stdout, "%s", messageWithNewLine.toLocal8Bit().constData()); Application::getInstance()->getLogger()->addMessage(messageWithNewLine.toLocal8Bit().constData()); }} Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), _statsExpanded(false), _nodeThread(new QThread(this)), _datagramProcessor(), _frameCount(0), _fps(120.0f), _justStarted(true), _voxelImporter(NULL), _wantToKillLocalVoxels(false), _audioScope(256, 200, true), _myAvatar(), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), _mouseX(0), _mouseY(0), _lastMouseMove(usecTimestampNow()), _mouseHidden(false), _seenMouseMove(false), _touchAvgX(0.0f), _touchAvgY(0.0f), _isTouchPressed(false), _mousePressed(false), _isHoverVoxel(false), _isHighlightVoxel(false), _chatEntryOn(false), _audio(&_audioScope, STARTUP_JITTER_SAMPLES), _enableProcessVoxelsThread(true), _voxelProcessor(), _voxelHideShowThread(&_voxels), _packetsPerSecond(0), _bytesPerSecond(0), _recentMaxPackets(0), _resetRecentMaxPacketsSoon(true), _pasteMode(false), _logger(new FileLogger(this)){ switchToResourcesParentIfRequired(); // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo("resources/info/ApplicationInfo.ini", QSettings::IniFormat); // set the associated application properties applicationInfo.beginGroup("INFO"); qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); setApplicationName(applicationInfo.value("name").toString()); setApplicationVersion(BUILD_VERSION); setOrganizationName(applicationInfo.value("organizationName").toString()); setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); QSettings::setDefaultFormat(QSettings::IniFormat); _myAvatar = _avatarManager.getMyAvatar(); _applicationStartupTime = startup_time; QFontDatabase::addApplicationFont("resources/styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); qInstallMessageHandler(messageHandler); // call Menu getInstance static method to set up the menu _window->setMenuBar(Menu::getInstance()); unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast<const char**>(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); if (portStr) { listenPort = atoi(portStr); } // start the nodeThread so its event loop is running _nodeThread->start(); // make sure the node thread is given highest priority _nodeThread->setPriority(QThread::TimeCriticalPriority); // put the NodeList and datagram processing on the node thread NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort); nodeList->moveToThread(_nodeThread); _datagramProcessor.moveToThread(_nodeThread); // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams())); // put the audio processing on a separate thread QThread* audioThread = new QThread(this); _audio.moveToThread(audioThread); connect(audioThread, SIGNAL(started()), &_audio, SLOT(start())); audioThread->start(); connect(&nodeList->getDomainInfo(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&nodeList->getDomainInfo(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer))); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); _settings = new QSettings(this); // Check to see if the user passed in a command line option for loading a local // Voxel File. _voxelsFilename = getCmdOption(argc, constArgv, "-i"); #ifdef _WIN32 WSADATA WsaData; int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData); #endif // tell the NodeList instance who to tell the domain server we care about nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer); // connect to the packet sent signal of the _voxelEditSender and the _particleEditSender connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent); connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent); // move the silentNodeTimer to the _nodeThread QTimer* silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // send the billboard packet for our avatar every few seconds QTimer* billboardPacketTimer = new QTimer(); connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket); billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); _networkAccessManager = new QNetworkAccessManager(this); QNetworkDiskCache* cache = new QNetworkDiskCache(_networkAccessManager); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); _networkAccessManager->setCache(cache); _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); QFontDatabase fontDatabase; fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf"); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); // initialization continues in initializeGL when OpenGL context is ready // Tell our voxel edit sender about our known jurisdictions _voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions); _particleEditSender.setServerJurisdictions(&_particleServerJurisdictions); Particle::setVoxelEditPacketSender(&_voxelEditSender); Particle::setParticleEditPacketSender(&_particleEditSender); // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to // allow you to move a particle around in your hand _particleEditSender.setPacketsPerSecond(3000); // super high!! // Set the sixense filtering _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); checkVersion(); _overlays.init(_glWidget); // do this before scripts load // do this as late as possible so that all required subsystems are inialized loadScripts();} Application::~Application() { qInstallMessageHandler(NULL); // make sure we don't call the idle timer any more delete idleTimer; Menu::getInstance()->saveSettings(); _rearMirrorTools->saveSettings(_settings); _sharedVoxelSystem.changeTree(new VoxelTree); if (_voxelImporter) { _voxelImporter->saveSettings(_settings); delete _voxelImporter; } _settings->sync(); // let the avatar mixer know we're out MyAvatar::sendKillAvatar(); // ask the datagram processing thread to quit and wait until it is done _nodeThread->quit(); _nodeThread->wait(); // ask the audio thread to quit and wait until it is done _audio.thread()->quit(); _audio.thread()->wait(); _voxelProcessor.terminate(); _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); _particleEditSender.terminate(); storeSizeAndPosition(); saveScripts(); VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown Menu::getInstance()->deleteLater(); _myAvatar = NULL; delete _glWidget;} void Application::restoreSizeAndPosition() { QSettings* settings = new QSettings(this); QRect available = desktop()->availableGeometry(); settings->beginGroup("Window"); int x = (int)loadSetting(settings, "x", 0); int y = (int)loadSetting(settings, "y", 0); _window->move(x, y); int width = (int)loadSetting(settings, "width", available.width()); int height = (int)loadSetting(settings, "height", available.height()); _window->resize(width, height); settings->endGroup();} void Application::storeSizeAndPosition() { QSettings* settings = new QSettings(this); settings->beginGroup("Window"); settings->setValue("width", _window->rect().width()); settings->setValue("height", _window->rect().height()); settings->setValue("x", _window->pos().x()); settings->setValue("y", _window->pos().y()); settings->endGroup();} void Application::initializeGL() { qDebug( "Created Display Window."); // initialize glut for shape drawing; Qt apparently initializes it on OS X #ifndef __APPLE__ int argc = 0; glutInit(&argc, 0); #endif #ifdef WIN32 GLenum err = glewInit(); if (GLEW_OK != err) { /* Problem: glewInit failed, something is seriously wrong. */ qDebug("Error: %s\n", glewGetErrorString(err)); } qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); #endif // Before we render anything, let's set up our viewFrustumOffsetCamera with a sufficiently large // field of view and near and far clip to make it interesting. //viewFrustumOffsetCamera.setFieldOfView(90.0); _viewFrustumOffsetCamera.setNearClip(0.1f); _viewFrustumOffsetCamera.setFarClip(500.0f * TREE_SCALE); initDisplay(); qDebug( "Initialized Display."); init(); qDebug( "init() complete."); // create thread for parsing of voxel data independent of the main network and rendering threads _voxelProcessor.initialize(_enableProcessVoxelsThread); _voxelEditSender.initialize(_enableProcessVoxelsThread); _voxelHideShowThread.initialize(_enableProcessVoxelsThread); _particleEditSender.initialize(_enableProcessVoxelsThread); if (_enableProcessVoxelsThread) { qDebug("Voxel parsing thread created."); } // call our timer function every second QTimer* timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timer())); timer->start(1000); // call our idle function whenever we can idleTimer = new QTimer(this); connect(idleTimer, SIGNAL(timeout()), SLOT(idle())); idleTimer->start(0); _idleLoopStdev.reset(); if (_justStarted) { float startupTime = (usecTimestampNow() - usecTimestamp(&_applicationStartupTime)) / 1000000.0; _justStarted = false; qDebug("Startup time: %4.2f seconds.", startupTime); const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time"; // ask the Logstash class to record the startup time Logging::stashValue(STAT_TYPE_TIMER, LOGSTASH_INTERFACE_START_TIME_KEY, startupTime); } // update before the first render update(0.0f); InfoView::showFirstTime();} void Application::paintGL() { PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::paintGL()"); glEnable(GL_LINE_SMOOTH); if (OculusManager::isConnected()) { _myCamera.setUpShift(0.0f); _myCamera.setDistance(0.0f); _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { _myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness(0.0f); float headHeight = _myAvatar->getHead()->calculateAverageEyePosition().y - _myAvatar->getPosition().y; _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale()); _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0)); _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); } // Update camera position _myCamera.update( 1.f/_fps ); // Note: whichCamera is used to pick between the normal camera myCamera for our // main camera, vs, an alternate camera. The alternate camera we support right now // is the viewFrustumOffsetCamera. But theoretically, we could use this same mechanism // to add other cameras. //Lines 1–500 of 3772. Load more
// // Application.cpp // interface // // Created by Andrzej Kapolka on 5/10/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #ifdef WIN32 #include <Systime.h> #endif #include <sstream> #include <stdlib.h> #include <cmath> #include <math.h> #include <glm/gtx/component_wise.hpp> #include <glm/gtx/quaternion.hpp> #include <glm/gtx/vector_angle.hpp> // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" #include <QActionGroup> #include <QColorDialog> #include <QDesktopWidget> #include <QCheckBox> #include <QImage> #include <QKeyEvent> #include <QMainWindow> #include <QMenuBar> #include <QMouseEvent> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QNetworkDiskCache> #include <QOpenGLFramebufferObject> #include <QObject> #include <QWheelEvent> #include <QSettings> #include <QShortcut> #include <QTimer> #include <QUrl> #include <QtDebug> #include <QFileDialog> #include <QDesktopServices> #include <QXmlStreamReader> #include <QXmlStreamAttributes> #include <QMediaPlayer> #include <QMimeData> #include <QMessageBox> #include <AccountManager.h> #include <AudioInjector.h> #include <Logging.h> #include <OctalCode.h> #include <PacketHeaders.h> #include <ParticlesScriptingInterface.h> #include <PerfStat.h> #include <UUID.h> #include <VoxelSceneStats.h> #include "Application.h" #include "ClipboardScriptingInterface.h" #include "InterfaceVersion.h" #include "Menu.h" #include "MenuScriptingInterface.h" #include "Util.h" #include "devices/OculusManager.h" #include "devices/TV3DManager.h" #include "renderer/ProgramObject.h" #include "ui/TextRenderer.h" #include "InfoView.h" #include "ui/Snapshot.h" using namespace std; // Starfield information static unsigned STARFIELD_NUM_STARS = 50000; static unsigned STARFIELD_SEED = 1; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff // in the idle loop? (60 FPS is default) static QTimer* idleTimer = NULL; const int STARTUP_JITTER_SAMPLES = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / 2; // Startup optimistically with small jitter buffer that // will start playback on the second received audio packet. const int MIRROR_VIEW_TOP_PADDING = 5; const int MIRROR_VIEW_LEFT_PADDING = 10; const int MIRROR_VIEW_WIDTH = 265; const int MIRROR_VIEW_HEIGHT = 215; const float MIRROR_FULLSCREEN_DISTANCE = 0.35f; const float MIRROR_REARVIEW_DISTANCE = 0.65f; const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; const float MIRROR_FIELD_OF_VIEW = 30.0f; const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; const int STATS_PELS_PER_LINE = 20; const QString CUSTOM_URL_SCHEME = "hifi:"; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.size() > 0) { QString messageWithNewLine = message + "\n"; fprintf(stdout, "%s", messageWithNewLine.toLocal8Bit().constData()); Application::getInstance()->getLogger()->addMessage(messageWithNewLine.toLocal8Bit().constData()); } } Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), _statsExpanded(false), _nodeThread(new QThread(this)), _datagramProcessor(), _frameCount(0), _fps(120.0f), _justStarted(true), _voxelImporter(NULL), _wantToKillLocalVoxels(false), _audioScope(256, 200, true), _myAvatar(), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), _mouseX(0), _mouseY(0), _lastMouseMove(usecTimestampNow()), _mouseHidden(false), _seenMouseMove(false), _touchAvgX(0.0f), _touchAvgY(0.0f), _isTouchPressed(false), _mousePressed(false), _isHoverVoxel(false), _isHighlightVoxel(false), _chatEntryOn(false), _audio(&_audioScope, STARTUP_JITTER_SAMPLES), _enableProcessVoxelsThread(true), _voxelProcessor(), _voxelHideShowThread(&_voxels), _packetsPerSecond(0), _bytesPerSecond(0), _recentMaxPackets(0), _resetRecentMaxPacketsSoon(true), _pasteMode(false), _logger(new FileLogger(this)) { switchToResourcesParentIfRequired(); // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo("resources/info/ApplicationInfo.ini", QSettings::IniFormat); // set the associated application properties applicationInfo.beginGroup("INFO"); qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); setApplicationName(applicationInfo.value("name").toString()); setApplicationVersion(BUILD_VERSION); setOrganizationName(applicationInfo.value("organizationName").toString()); setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); QSettings::setDefaultFormat(QSettings::IniFormat); _myAvatar = _avatarManager.getMyAvatar(); _applicationStartupTime = startup_time; QFontDatabase::addApplicationFont("resources/styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); qInstallMessageHandler(messageHandler); // call Menu getInstance static method to set up the menu _window->setMenuBar(Menu::getInstance()); unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast<const char**>(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); if (portStr) { listenPort = atoi(portStr); } // start the nodeThread so its event loop is running _nodeThread->start(); // make sure the node thread is given highest priority _nodeThread->setPriority(QThread::TimeCriticalPriority); // put the NodeList and datagram processing on the node thread NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort); nodeList->moveToThread(_nodeThread); _datagramProcessor.moveToThread(_nodeThread); // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams())); // put the audio processing on a separate thread QThread* audioThread = new QThread(this); _audio.moveToThread(audioThread); connect(audioThread, SIGNAL(started()), &_audio, SLOT(start())); audioThread->start(); connect(&nodeList->getDomainInfo(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&nodeList->getDomainInfo(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer))); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); _settings = new QSettings(this); // Check to see if the user passed in a command line option for loading a local // Voxel File. _voxelsFilename = getCmdOption(argc, constArgv, "-i"); #ifdef _WIN32 WSADATA WsaData; int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData); #endif // tell the NodeList instance who to tell the domain server we care about nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer); // connect to the packet sent signal of the _voxelEditSender and the _particleEditSender connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent); connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent); // move the silentNodeTimer to the _nodeThread QTimer* silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // send the billboard packet for our avatar every few seconds QTimer* billboardPacketTimer = new QTimer(); connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket); billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); _networkAccessManager = new QNetworkAccessManager(this); QNetworkDiskCache* cache = new QNetworkDiskCache(_networkAccessManager); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); _networkAccessManager->setCache(cache); _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); QFontDatabase fontDatabase; fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf"); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); // initialization continues in initializeGL when OpenGL context is ready // Tell our voxel edit sender about our known jurisdictions _voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions); _particleEditSender.setServerJurisdictions(&_particleServerJurisdictions); Particle::setVoxelEditPacketSender(&_voxelEditSender); Particle::setParticleEditPacketSender(&_particleEditSender); // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to // allow you to move a particle around in your hand _particleEditSender.setPacketsPerSecond(3000); // super high!! // Set the sixense filtering _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); checkVersion(); _overlays.init(_glWidget); // do this before scripts load // do this as late as possible so that all required subsystems are inialized loadScripts(); } Application::~Application() { qInstallMessageHandler(NULL); // make sure we don't call the idle timer any more delete idleTimer; Menu::getInstance()->saveSettings(); _rearMirrorTools->saveSettings(_settings); _sharedVoxelSystem.changeTree(new VoxelTree); if (_voxelImporter) { _voxelImporter->saveSettings(_settings); delete _voxelImporter; } _settings->sync(); // let the avatar mixer know we're out MyAvatar::sendKillAvatar(); // ask the datagram processing thread to quit and wait until it is done _nodeThread->quit(); _nodeThread->wait(); // ask the audio thread to quit and wait until it is done _audio.thread()->quit(); _audio.thread()->wait(); _voxelProcessor.terminate(); _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); _particleEditSender.terminate(); storeSizeAndPosition(); saveScripts(); VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown Menu::getInstance()->deleteLater(); _myAvatar = NULL; delete _glWidget; } void Application::restoreSizeAndPosition() { QSettings* settings = new QSettings(this); QRect available = desktop()->availableGeometry(); settings->beginGroup("Window"); int x = (int)loadSetting(settings, "x", 0); int y = (int)loadSetting(settings, "y", 0); _window->move(x, y); int width = (int)loadSetting(settings, "width", available.width()); int height = (int)loadSetting(settings, "height", available.height()); _window->resize(width, height); settings->endGroup(); } void Application::storeSizeAndPosition() { QSettings* settings = new QSettings(this); settings->beginGroup("Window"); settings->setValue("width", _window->rect().width()); settings->setValue("height", _window->rect().height()); settings->setValue("x", _window->pos().x()); settings->setValue("y", _window->pos().y()); settings->endGroup(); } void Application::initializeGL() { qDebug( "Created Display Window."); // initialize glut for shape drawing; Qt apparently initializes it on OS X #ifndef __APPLE__ int argc = 0; glutInit(&argc, 0); #endif #ifdef WIN32 GLenum err = glewInit(); if (GLEW_OK != err) { /* Problem: glewInit failed, something is seriously wrong. */ qDebug("Error: %s\n", glewGetErrorString(err)); } qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); #endif // Before we render anything, let's set up our viewFrustumOffsetCamera with a sufficiently large // field of view and near and far clip to make it interesting. //viewFrustumOffsetCamera.setFieldOfView(90.0); _viewFrustumOffsetCamera.setNearClip(0.1f); _viewFrustumOffsetCamera.setFarClip(500.0f * TREE_SCALE); initDisplay(); qDebug( "Initialized Display."); init(); qDebug( "init() complete."); // create thread for parsing of voxel data independent of the main network and rendering threads _voxelProcessor.initialize(_enableProcessVoxelsThread); _voxelEditSender.initialize(_enableProcessVoxelsThread); _voxelHideShowThread.initialize(_enableProcessVoxelsThread); _particleEditSender.initialize(_enableProcessVoxelsThread); if (_enableProcessVoxelsThread) { qDebug("Voxel parsing thread created."); } // call our timer function every second QTimer* timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timer())); timer->start(1000); // call our idle function whenever we can idleTimer = new QTimer(this); connect(idleTimer, SIGNAL(timeout()), SLOT(idle())); idleTimer->start(0); _idleLoopStdev.reset(); if (_justStarted) { float startupTime = (usecTimestampNow() - usecTimestamp(&_applicationStartupTime)) / 1000000.0; _justStarted = false; qDebug("Startup time: %4.2f seconds.", startupTime); const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time"; // ask the Logstash class to record the startup time Logging::stashValue(STAT_TYPE_TIMER, LOGSTASH_INTERFACE_START_TIME_KEY, startupTime); } // update before the first render update(0.0f); InfoView::showFirstTime(); } void Application::paintGL() { PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::paintGL()"); glEnable(GL_LINE_SMOOTH); if (OculusManager::isConnected()) { _myCamera.setUpShift(0.0f); _myCamera.setDistance(0.0f); _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { _myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness(0.0f); float headHeight = _myAvatar->getHead()->calculateAverageEyePosition().y - _myAvatar->getPosition().y; _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale()); _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0)); _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); } // Update camera position _myCamera.update( 1.f/_fps ); // Note: whichCamera is used to pick between the normal camera myCamera for our // main camera, vs, an alternate camera. The alternate camera we support right now // is the viewFrustumOffsetCamera. But theoretically, we could use this same mechanism // to add other cameras. //