3339 lines · 135.0 KB · C++
RawDownload
////  Application.cpp//  interface////  Created by Andrzej Kapolka on 5/10/13.//  Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include <sstream> #include <stdlib.h>#include <cmath> #ifdef _WIN32#include "Syssocket.h"#include "Systime.h"#else#include <sys/time.h>#include <arpa/inet.h>#include <ifaddrs.h>#endif #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 <QOpenGLFramebufferObject>#include <QWheelEvent>#include <QSettings>#include <QShortcut>#include <QTimer>#include <QUrl>#include <QtDebug>#include <QFileDialog>#include <QDesktopServices> #include <NodeTypes.h>#include <AudioInjectionManager.h>#include <AudioInjector.h>#include <Logstash.h>#include <OctalCode.h>#include <PacketHeaders.h>#include <PairingHandler.h>#include <PerfStat.h> #include <VoxelSceneStats.h> #include "Application.h"#include "LogDisplay.h"#include "Menu.h"#include "Swatch.h"#include "Util.h"#include "devices/LeapManager.h"#include "devices/OculusManager.h"#include "renderer/ProgramObject.h"#include "ui/TextRenderer.h" using namespace std; //  Starfield informationstatic char STAR_FILE[] = "http://s3-us-west-1.amazonaws.com/highfidelity/stars.txt";static char STAR_CACHE_FILE[] = "cachedStars.txt"; 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 = PACKET_LENGTH_SAMPLES_PER_CHANNEL / 2;                                                 //  Startup optimistically with small jitter buffer that                                                  //  will start playback on the second received audio packet.  void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) {    fprintf(stdout, "%s", message.toLocal8Bit().constData());    LogDisplay::instance.addMessage(message.toLocal8Bit().constData());} Application::Application(int& argc, char** argv, timeval &startup_time) :        QApplication(argc, argv),        _window(new QMainWindow(desktop())),        _glWidget(new GLCanvas()),        _displayLevels(false),        _frameCount(0),        _fps(120.0f),        _justStarted(true),        _voxelImporter(_window),        _wantToKillLocalVoxels(false),        _audioScope(256, 200, true),        _mouseX(0),        _mouseY(0),        _touchAvgX(0.0f),        _touchAvgY(0.0f),        _isTouchPressed(false),        _yawFromTouch(0.0f),        _pitchFromTouch(0.0f),        _mousePressed(false),        _isHoverVoxel(false),        _isHoverVoxelSounding(false),        _mouseVoxelScale(1.0f / 1024.0f),        _justEditedVoxel(false),        _isLookingAtOtherAvatar(false),        _lookatIndicatorScale(1.0f),        _perfStatsOn(false),        _chatEntryOn(false),        _oculusProgram(0),        _oculusDistortionScale(1.25),#ifndef _WIN32        _audio(&_audioScope, STARTUP_JITTER_SAMPLES),#endif        _stopNetworkReceiveThread(false),          _voxelProcessor(),        _voxelEditSender(this),        _packetCount(0),        _packetsPerSecond(0),        _bytesPerSecond(0),        _bytesCount(0),        _swatch(NULL),        _pasteMode(false){    _applicationStartupTime = startup_time;    _window->setWindowTitle("Interface");        qInstallMessageHandler(messageHandler);        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);    }        NodeList::createInstance(NODE_TYPE_AGENT, listenPort);        NodeList::getInstance()->addHook(&_voxels);    NodeList::getInstance()->addHook(this);         // network receive thread and voxel parsing thread are both controlled by the --nonblocking command line    _enableProcessVoxelsThread = _enableNetworkThread = !cmdOptionExists(argc, constArgv, "--nonblocking");    if (!_enableNetworkThread) {        NodeList::getInstance()->getNodeSocket()->setBlocking(false);    }        // setup QSettings    #ifdef Q_OS_MAC    QString resourcesPath = QCoreApplication::applicationDirPath() + "/../Resources";#else    QString resourcesPath = QCoreApplication::applicationDirPath() + "/resources";#endif        // read the ApplicationInfo.ini file for Name/Version/Domain information    QSettings applicationInfo(resourcesPath + "/info/ApplicationInfo.ini", QSettings::IniFormat);        // set the associated application properties    applicationInfo.beginGroup("INFO");        setApplicationName(applicationInfo.value("name").toString());    setApplicationVersion(applicationInfo.value("version").toString());    setOrganizationName(applicationInfo.value("organizationName").toString());    setOrganizationDomain(applicationInfo.value("organizationDomain").toString());        _settings = new QSettings(this);        // check if there is a saved domain server hostname    // this must be done now instead of with the other setting checks to allow manual override with    // --domain or --local options    NodeList::getInstance()->loadData(_settings);        const char* domainIP = getCmdOption(argc, constArgv, "--domain");    if (domainIP) {        NodeList::getInstance()->setDomainIP(domainIP);    }        // Handle Local Domain testing with the --local command line    if (cmdOptionExists(argc, constArgv, "--local")) {        qDebug("Local Domain MODE!\n");                NodeList::getInstance()->setDomainIPToLocalhost();    }        // Check to see if the user passed in a command line option for loading a local    // Voxel File.    _voxelsFilename = getCmdOption(argc, constArgv, "-i");        // the callback for our instance of NodeList is attachNewHeadToNode    NodeList::getInstance()->linkedDataCreateCallback = &attachNewHeadToNode;        #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    const char nodeTypesOfInterest[] = {NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER};    NodeList::getInstance()->setNodeTypesOfInterest(nodeTypesOfInterest, sizeof(nodeTypesOfInterest));     // start the nodeList threads    NodeList::getInstance()->startSilentNodeRemovalThread();        _window->setCentralWidget(_glWidget);     // call Menu getInstance static method to set up the menu    _window->setMenuBar(Menu::getInstance());        _networkAccessManager = new QNetworkAccessManager(this);        QRect available = desktop()->availableGeometry();    _window->resize(available.size());    _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);} Application::~Application() {    NodeList::getInstance()->removeHook(&_voxels);    NodeList::getInstance()->removeHook(this);     _sharedVoxelSystem.changeTree(new VoxelTree);     _audio.shutdown();     delete Menu::getInstance();        delete _oculusProgram;    delete _settings;    delete _networkAccessManager;    delete _followMode;    delete _glWidget;} void Application::initializeGL() {    qDebug( "Created Display Window.\n" );        // initialize glut for shape drawing; Qt apparently initializes it on OS X    #ifndef __APPLE__    int argc = 0;    glutInit(&argc, 0);    #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.1);    _viewFrustumOffsetCamera.setFarClip(500.0 * TREE_SCALE);        initDisplay();    qDebug( "Initialized Display.\n" );        init();    qDebug( "Init() complete.\n" );        // Check to see if the user passed in a command line option for randomizing colors    bool wantColorRandomizer = !arguments().contains("--NoColorRandomizer");        // Check to see if the user passed in a command line option for loading a local    // Voxel File. If so, load it now.    if (!_voxelsFilename.isEmpty()) {        _voxels.loadVoxelsFile(_voxelsFilename.constData(), wantColorRandomizer);        qDebug("Local Voxel File loaded.\n");    }        // create thread for receipt of data via UDP    if (_enableNetworkThread) {        pthread_create(&_networkReceiveThread, NULL, networkReceive, NULL);        qDebug("Network receive thread created.\n");     }     // create thread for parsing of voxel data independent of the main network and rendering threads    _voxelProcessor.initialize(_enableProcessVoxelsThread);    _voxelEditSender.initialize(_enableProcessVoxelsThread);    if (_enableProcessVoxelsThread) {        qDebug("Voxel parsing thread created.\n");    }        // call terminate before exiting    connect(this, SIGNAL(aboutToQuit()), SLOT(terminate()));        // 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;        char title[50];        sprintf(title, "Interface: %4.2f seconds\n", startupTime);        qDebug("%s", title);        _window->setWindowTitle(title);                const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time";                // ask the Logstash class to record the startup time        Logstash::stashValue(STAT_TYPE_TIMER, LOGSTASH_INTERFACE_START_TIME_KEY, startupTime);    }        // update before the first render    update(0.0f);        // now that things are drawn - if this is an OS X release build we can check for an update#if defined(Q_OS_MAC) && defined(QT_NO_DEBUG)    Menu::getInstance()->checkForUpdates();#endif} void Application::paintGL() {    PerfStat("display");     glEnable(GL_LINE_SMOOTH);     if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {        _myCamera.setTightness     (100.0f);         _myCamera.setTargetPosition(_myAvatar.getUprightHeadPosition());        _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));            } else 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.getHeadJointPosition());        _myCamera.setTargetRotation(_myAvatar.getHead().getOrientation());        } else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {        _myCamera.setTightness(0.0f);  //  In first person, camera follows head exactly without delay        _myCamera.setTargetPosition(_myAvatar.getUprightEyeLevelPosition());        _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());            } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {        _myCamera.setTargetPosition(_myAvatar.getUprightHeadPosition());        _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());    }        // 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.    //    // Why have two cameras? Well, one reason is that because in the case of the renderViewFrustum()    // code, we want to keep the state of "myCamera" intact, so we can render what the view frustum of    // myCamera is. But we also want to do meaningful camera transforms on OpenGL for the offset camera    Camera whichCamera = _myCamera;     if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) {                ViewFrustumOffset viewFrustumOffset = Menu::getInstance()->getViewFrustumOffset();         // set the camera to third-person view but offset so we can see the frustum        _viewFrustumOffsetCamera.setTargetPosition(_myCamera.getTargetPosition());        _viewFrustumOffsetCamera.setTargetRotation(_myCamera.getTargetRotation() * glm::quat(glm::radians(glm::vec3(            viewFrustumOffset.pitch, viewFrustumOffset.yaw, viewFrustumOffset.roll))));        _viewFrustumOffsetCamera.setUpShift(viewFrustumOffset.up);        _viewFrustumOffsetCamera.setDistance(viewFrustumOffset.distance);        _viewFrustumOffsetCamera.initialize(); // force immediate snap to ideal position and orientation        _viewFrustumOffsetCamera.update(1.f/_fps);        whichCamera = _viewFrustumOffsetCamera;    }             if (OculusManager::isConnected()) {        displayOculus(whichCamera);            } else {        _glowEffect.prepare();                 glMatrixMode(GL_MODELVIEW);        glPushMatrix();        glLoadIdentity();        displaySide(whichCamera);        glPopMatrix();                _glowEffect.render();                displayOverlay();    }        _frameCount++;} void Application::resetCamerasOnResizeGL(Camera& camera, int width, int height) {    float aspectRatio = ((float)width/(float)height); // based on screen resize        if (OculusManager::isConnected()) {        // more magic numbers; see Oculus SDK docs, p. 32        camera.setAspectRatio(aspectRatio *= 0.5);        camera.setFieldOfView(2 * atan((0.0468 * _oculusDistortionScale) / 0.041) * (180 / PIf));    } else {        camera.setAspectRatio(aspectRatio);        camera.setFieldOfView(Menu::getInstance()->getFieldOfView());    }} void Application::resizeGL(int width, int height) {    resetCamerasOnResizeGL(_viewFrustumOffsetCamera, width, height);    resetCamerasOnResizeGL(_myCamera, width, height);     // Tell our viewFrustum about this change, using the application camera    loadViewFrustum(_myCamera, _viewFrustum);     glViewport(0, 0, width, height); // shouldn't this account for the menu???     updateProjectionMatrix();    glLoadIdentity();} void Application::updateProjectionMatrix() {    glMatrixMode(GL_PROJECTION);    glLoadIdentity();        float left, right, bottom, top, nearVal, farVal;    glm::vec4 nearClipPlane, farClipPlane;    _viewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);        // If we're in Display Frustum mode, then we want to use the slightly adjust near/far clip values of the    // _viewFrustumOffsetCamera, so that we can see more of the application content in the application's frustum    if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) {        nearVal = _viewFrustumOffsetCamera.getNearClip();        farVal = _viewFrustumOffsetCamera.getFarClip();    }    glFrustum(left, right, bottom, top, nearVal, farVal);        glMatrixMode(GL_MODELVIEW);} void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes,                                              const char* nodeTypes, int numNodeTypes) {    Application* self = getInstance();    for (int i = 0; i < numNodeTypes; ++i) {         // Intercept data to voxel server when voxels are disabled        if (nodeTypes[i] == NODE_TYPE_VOXEL_SERVER && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {            continue;        }         // Perform the broadcast for one type        int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(broadcastData, dataBytes, & nodeTypes[i], 1);         // Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise)        BandwidthMeter::ChannelIndex channel;        switch (nodeTypes[i]) {            case NODE_TYPE_AGENT:            case NODE_TYPE_AVATAR_MIXER:                channel = BandwidthMeter::AVATARS;                break;            case NODE_TYPE_VOXEL_SERVER:                channel = BandwidthMeter::VOXELS;                break;            default:                continue;        }        self->_bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * dataBytes);     }} void Application::keyPressEvent(QKeyEvent* event) {    if (activeWindow() == _window) {        if (_chatEntryOn) {            if (_chatEntry.keyPressEvent(event)) {                _myAvatar.setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ?                                      DELETE_KEY_DOWN : INSERT_KEY_DOWN);                _myAvatar.setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR));                            } else {                _myAvatar.setChatMessage(_chatEntry.getContents());                _chatEntry.clear();                _chatEntryOn = false;                setMenuShortcutsEnabled(true);            }            return;        } 

Lines 1–500 of 3339.