overte/interface/src/Application.cpp
2014-11-07 13:57:20 -08:00

4377 lines
173 KiB
C++

//
// Application.cpp
// interface/src
//
// Created by Andrzej Kapolka on 5/10/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 <sstream>
#include <stdlib.h>
#include <cmath>
#include <math.h>
#include <glm/glm.hpp>
#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 <QInputDialog>
#include <QKeyEvent>
#include <QMenuBar>
#include <QMouseEvent>
#include <QNetworkReply>
#include <QNetworkDiskCache>
#include <QOpenGLFramebufferObject>
#include <QObject>
#include <QWheelEvent>
#include <QSettings>
#include <QShortcut>
#include <QTimer>
#include <QUrl>
#include <QWindow>
#include <QtDebug>
#include <QFileDialog>
#include <QDesktopServices>
#include <QXmlStreamReader>
#include <QXmlStreamAttributes>
#include <QMediaPlayer>
#include <QMimeData>
#include <QMessageBox>
#include <AddressManager.h>
#include <AccountManager.h>
#include <AudioInjector.h>
#include <EntityScriptingInterface.h>
#include <HFActionEvent.h>
#include <HFBackEvent.h>
#include <LocalVoxelsList.h>
#include <LogHandler.h>
#include <NetworkAccessManager.h>
#include <OctalCode.h>
#include <OctreeSceneStats.h>
#include <PacketHeaders.h>
#include <PerfStat.h>
#include <ResourceCache.h>
#include <UserActivityLogger.h>
#include <UUID.h>
#include "Application.h"
#include "InterfaceVersion.h"
#include "Menu.h"
#include "ModelUploader.h"
#include "Util.h"
#include "devices/Leapmotion.h"
#include "devices/MIDIManager.h"
#include "devices/OculusManager.h"
#include "devices/TV3DManager.h"
#include "renderer/ProgramObject.h"
#include "gpu/Batch.h"
#include "gpu/GLBackend.h"
#include "scripting/AccountScriptingInterface.h"
#include "scripting/AudioDeviceScriptingInterface.h"
#include "scripting/ClipboardScriptingInterface.h"
#include "scripting/JoystickScriptingInterface.h"
#include "scripting/GlobalServicesScriptingInterface.h"
#include "scripting/LocationScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
#include "ui/DataWebDialog.h"
#include "ui/InfoView.h"
#include "ui/Snapshot.h"
#include "ui/Stats.h"
#include "ui/TextRenderer.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 qint64 MAXIMUM_CACHE_SIZE = 10737418240L; // 10GB
static QTimer* idleTimer = NULL;
const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml";
const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion";
const QString DEFAULT_SCRIPTS_JS_URL = "http://public.highfidelity.io/scripts/defaultScripts.js";
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message);
if (!logMessage.isEmpty()) {
Application::getInstance()->getLogger()->addMessage(qPrintable(logMessage));
}
}
QString& Application::resourcesPath() {
#ifdef Q_OS_MAC
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
#else
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
#endif
return staticResourcePath;
}
Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
QApplication(argc, argv),
_window(new MainWindow(desktop())),
_glWidget(new GLCanvas()),
_nodeThread(new QThread(this)),
_datagramProcessor(),
_undoStack(),
_undoStackScriptingInterface(&_undoStack),
_frameCount(0),
_fps(60.0f),
_justStarted(true),
_voxelImportDialog(NULL),
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_entities(true),
_entityCollisionSystem(),
_entityClipboardRenderer(false),
_entityClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
_lastQueriedViewFrustum(),
_lastQueriedTime(usecTimestampNow()),
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
_scaleMirror(1.0f),
_rotateMirror(0.0f),
_raiseMirror(0.0f),
_mouseX(0),
_mouseY(0),
_lastMouseMove(usecTimestampNow()),
_lastMouseMoveWasSimulated(false),
_mouseHidden(false),
_seenMouseMove(false),
_touchAvgX(0.0f),
_touchAvgY(0.0f),
_isTouchPressed(false),
_mousePressed(false),
_audio(),
_enableProcessVoxelsThread(true),
_octreeProcessor(),
_voxelHideShowThread(&_voxels),
_packetsPerSecond(0),
_bytesPerSecond(0),
_nodeBoundsDisplay(this),
_previousScriptLocation(),
_applicationOverlay(),
_runningScriptsWidget(NULL),
_runningScriptsWidgetWasVisible(false),
_trayIcon(new QSystemTrayIcon(_window)),
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow()),
_isVSyncOn(true),
_aboutToQuit(false)
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
// set the associated application properties
applicationInfo.beginGroup("INFO");
setApplicationName(applicationInfo.value("name").toString());
setApplicationVersion(BUILD_VERSION);
setOrganizationName(applicationInfo.value("organizationName").toString());
setOrganizationDomain(applicationInfo.value("organizationDomain").toString());
_logger = new FileLogger(this); // After setting organization name in order to get correct directory
QSettings::setDefaultFormat(QSettings::IniFormat);
_myAvatar = _avatarManager.getMyAvatar();
_applicationStartupTime = startup_time;
QFontDatabase::addApplicationFont(Application::resourcesPath() + "styles/Inconsolata.otf");
_window->setWindowTitle("Interface");
qInstallMessageHandler(messageHandler);
qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());
// call Menu getInstance static method to set up the menu
_window->setMenuBar(Menu::getInstance());
_runningScriptsWidget = new RunningScriptsWidget(_window);
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();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDialogDisplayedFlag);
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
QTimer* locationUpdateTimer = new QTimer(this);
connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer);
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
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, _myAvatar, &MyAvatar::setSessionUUID);
connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset);
// connect to appropriate slots on AccountManager
AccountManager& accountManager = AccountManager::getInstance();
const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000;
QTimer* balanceUpdateTimer = new QTimer(this);
connect(balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance);
balanceUpdateTimer->start(BALANCE_UPDATE_INTERVAL_MSECS);
connect(&accountManager, &AccountManager::balanceChanged, this, &Application::updateWindowTitle);
connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain);
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
// once we have a profile in account manager make sure we generate a new keypair
connect(&accountManager, &AccountManager::profileChanged, &accountManager, &AccountManager::generateNewKeypair);
// 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);
UserActivityLogger::getInstance().launch(applicationVersion());
// once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
AddressManager& addressManager = AddressManager::getInstance();
// use our MyAvatar position and quat for address manager path
addressManager.setPositionGetter(getPositionForPath);
addressManager.setOrientationGetter(getOrientationForPath);
// handle domain change signals from AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequiredToHostname,
this, &Application::changeDomainHostname);
connect(&addressManager, &AddressManager::possibleDomainChangeRequiredViaICEForID,
&domainHandler, &DomainHandler::setIceServerHostnameAndID);
_settings = new QSettings(this);
_numChangedSettings = 0;
// 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::EntityServer
<< NodeType::MetavoxelServer);
// connect to the packet sent signal of the _voxelEditSender and the _entityEditSender
connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
// move the silentNodeTimer to the _nodeThread
QTimer* silentNodeTimer = new QTimer();
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS);
silentNodeTimer->moveToThread(_nodeThread);
// 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);
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
networkAccessManager.setCache(cache);
ResourceCache::setRequestLimit(3);
_window->setCentralWidget(_glWidget);
restoreSizeAndPosition();
_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);
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
// 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 an entity around in your hand
_entityEditSender.setPacketsPerSecond(3000); // super high!!
checkVersion();
_overlays.init(_glWidget); // do this before scripts load
LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree());
LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript);
connect(this, SIGNAL(aboutToQuit()), this, SLOT(saveScripts()));
connect(this, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));
// check first run...
QVariant firstRunValue = _settings->value("firstRun",QVariant(true));
if (firstRunValue.isValid() && firstRunValue.toBool()) {
qDebug() << "This is a first run...";
// clear the scripts, and set out script to our default scripts
clearScriptsBeforeRunning();
loadScript(DEFAULT_SCRIPTS_JS_URL);
QMutexLocker locker(&_settingsMutex);
_settings->setValue("firstRun",QVariant(false));
} else {
// do this as late as possible so that all required subsystems are initialized
loadScripts();
QMutexLocker locker(&_settingsMutex);
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
}
connect(_window, &MainWindow::windowGeometryChanged,
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
_trayIcon->show();
// set the local loopback interface for local sounds from audio scripts
AudioScriptingInterface::getInstance().setLocalLoopbackInterface(&_audio);
#ifdef HAVE_RTMIDI
// setup the MIDIManager
MIDIManager& midiManagerInstance = MIDIManager::getInstance();
midiManagerInstance.openDefaultPort();
#endif
this->installEventFilter(this);
}
void Application::aboutToQuit() {
_aboutToQuit = true;
}
Application::~Application() {
qInstallMessageHandler(NULL);
saveSettings();
storeSizeAndPosition();
int DELAY_TIME = 1000;
UserActivityLogger::getInstance().close(DELAY_TIME);
// make sure we don't call the idle timer any more
delete idleTimer;
_sharedVoxelSystem.changeTree(new VoxelTree);
delete _voxelImportDialog;
// 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();
// stop the audio process
QMetaObject::invokeMethod(&_audio, "stop");
// ask the audio thread to quit and wait until it is done
_audio.thread()->quit();
_audio.thread()->wait();
// kill any audio injectors that are still around
AudioScriptingInterface::getInstance().stopAllInjectors();
_octreeProcessor.terminate();
_voxelHideShowThread.terminate();
_voxelEditSender.terminate();
_entityEditSender.terminate();
VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown
Menu::getInstance()->deleteLater();
_myAvatar = NULL;
delete _glWidget;
}
void Application::saveSettings() {
Menu::getInstance()->saveSettings();
_rearMirrorTools->saveSettings(_settings);
if (_voxelImportDialog) {
_voxelImportDialog->saveSettings(_settings);
}
_settings->sync();
_numChangedSettings = 0;
}
void Application::restoreSizeAndPosition() {
QRect available = desktop()->availableGeometry();
QMutexLocker locker(&_settingsMutex);
_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() {
QMutexLocker locker(&_settingsMutex);
_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__
static bool isInitialized = false;
if (isInitialized) {
return;
} else {
isInitialized = true;
}
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));
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
}
#endif
#if defined(Q_OS_LINUX)
// TODO: Write the correct code for Linux...
/* if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
}*/
#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(DEFAULT_NEAR_CLIP);
_viewFrustumOffsetCamera.setFarClip(DEFAULT_FAR_CLIP);
initDisplay();
qDebug( "Initialized Display.");
init();
qDebug( "init() complete.");
// create thread for parsing of voxel data independent of the main network and rendering threads
_octreeProcessor.initialize(_enableProcessVoxelsThread);
_voxelEditSender.initialize(_enableProcessVoxelsThread);
_voxelHideShowThread.initialize(_enableProcessVoxelsThread);
_entityEditSender.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 = (float)_applicationStartupTime.elapsed() / 1000.0;
_justStarted = false;
qDebug("Startup time: %4.2f seconds.", startupTime);
}
// update before the first render
update(1.f / _fps);
InfoView::showFirstTime(INFO_HELP_PATH);
}
void Application::paintGL() {
PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("paintGL");
PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings));
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::paintGL()");
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
// Otherwise, it must rebuild the FBOs
if (OculusManager::isConnected()) {
_textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize());
} else {
QSize fbSize = _glWidget->getDeviceSize() * getRenderResolutionScale();
_textureCache.setFrameBufferSize(fbSize);
}
glEnable(GL_LINE_SMOOTH);
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
if (!OculusManager::isConnected()) {
// If there isn't an HMD, match exactly to avatar's head
_myCamera.setPosition(_myAvatar->getHead()->getEyePosition());
_myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation());
} else {
// For an HMD, set the base position and orientation to that of the avatar body
_myCamera.setPosition(_myAvatar->getDefaultEyePosition());
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation());
}
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
static const float THIRD_PERSON_CAMERA_DISTANCE = 1.5f;
_myCamera.setPosition(_myAvatar->getDefaultEyePosition() +
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, 1.0f) * THIRD_PERSON_CAMERA_DISTANCE * _myAvatar->getScale());
if (OculusManager::isConnected()) {
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation());
} else {
_myCamera.setRotation(_myAvatar->getHead()->getOrientation());
}
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
//Only behave like a true mirror when in the OR
if (OculusManager::isConnected()) {
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(_myAvatar->getHead()->getEyePosition() +
glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0) +
(_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
} else {
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(_myAvatar->getHead()->getEyePosition() +
glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0) +
(_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
}
// Update camera position
if (!OculusManager::isConnected()) {
_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
glm::quat frustumRotation = glm::quat(glm::radians(glm::vec3(viewFrustumOffset.pitch, viewFrustumOffset.yaw, viewFrustumOffset.roll)));
_viewFrustumOffsetCamera.setPosition(_myCamera.getPosition() +
frustumRotation * glm::vec3(0.0f, viewFrustumOffset.up, -viewFrustumOffset.distance));
_viewFrustumOffsetCamera.setRotation(_myCamera.getRotation() * frustumRotation);
_viewFrustumOffsetCamera.update(1.f/_fps);
whichCamera = &_viewFrustumOffsetCamera;
}
if (Menu::getInstance()->getShadowsEnabled()) {
updateShadowMap();
}
if (OculusManager::isConnected()) {
//Clear the color buffer to ensure that there isnt any residual color
//Left over from when OR was not connected.
glClear(GL_COLOR_BUFFER_BIT);
//When in mirror mode, use camera rotation. Otherwise, use body rotation
if (whichCamera->getMode() == CAMERA_MODE_MIRROR) {
OculusManager::display(whichCamera->getRotation(), whichCamera->getPosition(), *whichCamera);
} else {
OculusManager::display(_myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), *whichCamera);
}
_myCamera.update(1.f / _fps);
} else if (TV3DManager::isConnected()) {
TV3DManager::display(*whichCamera);
} else {
_glowEffect.prepare();
// Viewport is assigned to the size of the framebuffer
QSize size = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->size();
glViewport(0, 0, size.width(), size.height());
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
displaySide(*whichCamera);
glPopMatrix();
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
_rearMirrorTools->render(true);
} else if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
renderRearViewMirror(_mirrorViewRect);
}
_glowEffect.render();
{
PerformanceTimer perfTimer("renderOverlay");
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
_applicationOverlay.renderOverlay(true);
if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) {
_applicationOverlay.displayOverlayTexture();
}
}
}
_frameCount++;
}
void Application::resetCamerasOnResizeGL(Camera& camera, int width, int height) {
if (OculusManager::isConnected()) {
OculusManager::configureCamera(camera, width, height);
} else if (TV3DManager::isConnected()) {
TV3DManager::configureCamera(camera, width, height);
} else {
camera.setAspectRatio((float)width / height);
camera.setFieldOfView(Menu::getInstance()->getFieldOfView());
}
}
void Application::resizeGL(int width, int height) {
resetCamerasOnResizeGL(_viewFrustumOffsetCamera, width, height);
resetCamerasOnResizeGL(_myCamera, width, height);
glViewport(0, 0, width, height); // shouldn't this account for the menu???
updateProjectionMatrix();
glLoadIdentity();
// 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;
Stats::getInstance()->resetWidth(width, horizontalOffset);
}
void Application::updateProjectionMatrix() {
updateProjectionMatrix(_myCamera);
}
void Application::updateProjectionMatrix(Camera& camera, bool updateViewFrustum) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float left, right, bottom, top, nearVal, farVal;
glm::vec4 nearClipPlane, farClipPlane;
// Tell our viewFrustum about this change, using the application camera
if (updateViewFrustum) {
loadViewFrustum(camera, _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();
}
} else {
ViewFrustum tempViewFrustum;
loadViewFrustum(camera, tempViewFrustum);
tempViewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
}
glFrustum(left, right, bottom, top, nearVal, farVal);
// save matrix
glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)&_projectionMatrix);
glMatrixMode(GL_MODELVIEW);
}
void Application::controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
foreach(NodeType_t type, destinationNodeTypes) {
// Intercept data to voxel server when voxels are disabled
if (type == NodeType::VoxelServer && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
continue;
}
// Perform the broadcast for one type
int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(packet, NodeSet() << type);
// Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise)
BandwidthMeter::ChannelIndex channel;
switch (type) {
case NodeType::Agent:
case NodeType::AvatarMixer:
channel = BandwidthMeter::AVATARS;
break;
case NodeType::VoxelServer:
case NodeType::EntityServer:
channel = BandwidthMeter::VOXELS;
break;
default:
continue;
}
_bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * packet.size());
}
}
bool Application::event(QEvent* event) {
// handle custom URL
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
if (!fileEvent->url().isEmpty()) {
AddressManager::getInstance().handleLookupString(fileEvent->url().toLocalFile());
}
return false;
}
if (HFActionEvent::types().contains(event->type())) {
_controllerScriptingInterface.handleMetaEvent(static_cast<HFMetaEvent*>(event));
}
return QApplication::event(event);
}
bool Application::eventFilter(QObject* object, QEvent* event) {
if (event->type() == QEvent::ShortcutOverride) {
// Filter out captured keys before they're used for shortcut actions.
if (_controllerScriptingInterface.isKeyCaptured(static_cast<QKeyEvent*>(event))) {
event->accept();
return true;
}
}
return false;
}
void Application::keyPressEvent(QKeyEvent* event) {
_keysPressed.insert(event->key());
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isKeyCaptured(event)) {
return;
}
if (activeWindow() == _window) {
bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier);
bool isMeta = event->modifiers().testFlag(Qt::ControlModifier);
bool isOption = event->modifiers().testFlag(Qt::AltModifier);
switch (event->key()) {
break;
case Qt::Key_BracketLeft:
case Qt::Key_BracketRight:
case Qt::Key_BraceLeft:
case Qt::Key_BraceRight:
case Qt::Key_ParenLeft:
case Qt::Key_ParenRight:
case Qt::Key_Less:
case Qt::Key_Greater:
case Qt::Key_Comma:
case Qt::Key_Period:
Menu::getInstance()->handleViewFrustumOffsetKeyModifier(event->key());
break;
case Qt::Key_L:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::LodTools);
} else if (isMeta) {
Menu::getInstance()->triggerOption(MenuOption::Log);
}
break;
case Qt::Key_E:
case Qt::Key_PageUp:
if (!_myAvatar->getDriveKeys(UP)) {
_myAvatar->jump();
}
_myAvatar->setDriveKeys(UP, 1.f);
break;
case Qt::Key_Asterisk:
Menu::getInstance()->triggerOption(MenuOption::Stars);
break;
case Qt::Key_C:
case Qt::Key_PageDown:
_myAvatar->setDriveKeys(DOWN, 1.f);
break;
case Qt::Key_W:
if (isOption && !isShifted && !isMeta) {
Menu::getInstance()->triggerOption(MenuOption::Wireframe);
} else {
_myAvatar->setDriveKeys(FWD, 1.f);
}
break;
case Qt::Key_S:
if (isShifted && isMeta && !isOption) {
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
} else if (isOption && !isShifted && !isMeta) {
Menu::getInstance()->triggerOption(MenuOption::ScriptEditor);
} else if (!isOption && !isShifted && isMeta) {
takeSnapshot();
} else {
_myAvatar->setDriveKeys(BACK, 1.f);
}
break;
case Qt::Key_Apostrophe:
resetSensors();
break;
case Qt::Key_G:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::ObeyEnvironmentalGravity);
}
break;
case Qt::Key_A:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::Atmosphere);
} else {
_myAvatar->setDriveKeys(ROT_LEFT, 1.f);
}
break;
case Qt::Key_D:
if (!isMeta) {
_myAvatar->setDriveKeys(ROT_RIGHT, 1.f);
}
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;
case Qt::Key_N:
if (isMeta) {
Menu::getInstance()->triggerOption(MenuOption::NameLocation);
}
break;
case Qt::Key_Up:
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (!isShifted) {
_scaleMirror *= 0.95f;
} else {
_raiseMirror += 0.05f;
}
} else {
_myAvatar->setDriveKeys(isShifted ? UP : FWD, 1.f);
}
break;
case Qt::Key_Down:
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (!isShifted) {
_scaleMirror *= 1.05f;
} else {
_raiseMirror -= 0.05f;
}
} else {
_myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1.f);
}
break;
case Qt::Key_Left:
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_rotateMirror += PI / 20.f;
} else {
_myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1.f);
}
break;
case Qt::Key_Right:
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_rotateMirror -= PI / 20.f;
} else {
_myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1.f);
}
break;
case Qt::Key_I:
if (isShifted) {
_myCamera.setEyeOffsetOrientation(glm::normalize(
glm::quat(glm::vec3(0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation()));
} else {
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0.001, 0));
}
updateProjectionMatrix();
break;
case Qt::Key_K:
if (isShifted) {
_myCamera.setEyeOffsetOrientation(glm::normalize(
glm::quat(glm::vec3(-0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation()));
} else {
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, -0.001, 0));
}
updateProjectionMatrix();
break;
case Qt::Key_J:
if (isShifted) {
_viewFrustum.setFocalLength(_viewFrustum.getFocalLength() - 0.1f);
if (TV3DManager::isConnected()) {
TV3DManager::configureCamera(_myCamera, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight());
}
} else {
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(-0.001, 0, 0));
}
updateProjectionMatrix();
break;
case Qt::Key_M:
if (isShifted) {
_viewFrustum.setFocalLength(_viewFrustum.getFocalLength() + 0.1f);
if (TV3DManager::isConnected()) {
TV3DManager::configureCamera(_myCamera, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight());
}
} else {
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0.001, 0, 0));
}
updateProjectionMatrix();
break;
case Qt::Key_U:
if (isShifted) {
_myCamera.setEyeOffsetOrientation(glm::normalize(
glm::quat(glm::vec3(0, 0, -0.002f)) * _myCamera.getEyeOffsetOrientation()));
} else {
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, -0.001));
}
updateProjectionMatrix();
break;
case Qt::Key_Y:
if (isShifted) {
_myCamera.setEyeOffsetOrientation(glm::normalize(
glm::quat(glm::vec3(0, 0, 0.002f)) * _myCamera.getEyeOffsetOrientation()));
} else {
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, 0.001));
}
updateProjectionMatrix();
break;
case Qt::Key_H:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::Mirror);
} else {
Menu::getInstance()->triggerOption(MenuOption::FullscreenMirror);
}
break;
case Qt::Key_Slash:
Menu::getInstance()->triggerOption(MenuOption::UserInterface);
break;
case Qt::Key_F:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::DisplayFrustum);
}
break;
case Qt::Key_V:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::Voxels);
}
break;
case Qt::Key_P:
Menu::getInstance()->triggerOption(MenuOption::FirstPerson);
break;
case Qt::Key_R:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode);
}
break;
case Qt::Key_Percent:
Menu::getInstance()->triggerOption(MenuOption::Stats);
break;
case Qt::Key_Plus:
_myAvatar->increaseSize();
break;
case Qt::Key_Minus:
_myAvatar->decreaseSize();
break;
case Qt::Key_Equal:
_myAvatar->resetSize();
break;
case Qt::Key_Space: {
if (!event->isAutoRepeat()) {
// this starts an HFActionEvent
HFActionEvent startActionEvent(HFActionEvent::startType(),
_viewFrustum.computePickRay(0.5f, 0.5f));
sendEvent(this, &startActionEvent);
}
break;
}
case Qt::Key_Escape: {
OculusManager::abandonCalibration();
if (!event->isAutoRepeat()) {
// this starts the HFCancelEvent
HFBackEvent startBackEvent(HFBackEvent::startType());
sendEvent(this, &startBackEvent);
}
break;
}
default:
event->ignore();
break;
}
}
}
void Application::keyReleaseEvent(QKeyEvent* event) {
_keysPressed.remove(event->key());
_controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isKeyCaptured(event)) {
return;
}
switch (event->key()) {
case Qt::Key_E:
case Qt::Key_PageUp:
_myAvatar->setDriveKeys(UP, 0.f);
break;
case Qt::Key_C:
case Qt::Key_PageDown:
_myAvatar->setDriveKeys(DOWN, 0.f);
break;
case Qt::Key_W:
_myAvatar->setDriveKeys(FWD, 0.f);
break;
case Qt::Key_S:
_myAvatar->setDriveKeys(BACK, 0.f);
break;
case Qt::Key_A:
_myAvatar->setDriveKeys(ROT_LEFT, 0.f);
break;
case Qt::Key_D:
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
case Qt::Key_Up:
_myAvatar->setDriveKeys(FWD, 0.f);
_myAvatar->setDriveKeys(UP, 0.f);
break;
case Qt::Key_Down:
_myAvatar->setDriveKeys(BACK, 0.f);
_myAvatar->setDriveKeys(DOWN, 0.f);
break;
case Qt::Key_Left:
_myAvatar->setDriveKeys(LEFT, 0.f);
_myAvatar->setDriveKeys(ROT_LEFT, 0.f);
break;
case Qt::Key_Right:
_myAvatar->setDriveKeys(RIGHT, 0.f);
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
case Qt::Key_Control:
case Qt::Key_Shift:
case Qt::Key_Meta:
case Qt::Key_Alt:
_myAvatar->clearDriveKeys();
break;
case Qt::Key_Space: {
if (!event->isAutoRepeat()) {
// this ends the HFActionEvent
HFActionEvent endActionEvent(HFActionEvent::endType(), _viewFrustum.computePickRay(0.5f, 0.5f));
sendEvent(this, &endActionEvent);
}
break;
}
case Qt::Key_Escape: {
if (!event->isAutoRepeat()) {
// this ends the HFCancelEvent
HFBackEvent endBackEvent(HFBackEvent::endType());
sendEvent(this, &endBackEvent);
}
break;
}
default:
event->ignore();
break;
}
}
void Application::focusOutEvent(QFocusEvent* event) {
// synthesize events for keys currently pressed, since we may not get their release events
foreach (int key, _keysPressed) {
QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier);
keyReleaseEvent(&event);
}
_keysPressed.clear();
}
void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
bool showMouse = true;
// Used by application overlay to determine how to draw cursor(s)
_lastMouseMoveWasSimulated = deviceID > 0;
// If this mouse move event is emitted by a controller, dont show the mouse cursor
if (_lastMouseMoveWasSimulated) {
showMouse = false;
}
if (!_aboutToQuit) {
_entities.mouseMoveEvent(event, deviceID);
}
_controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isMouseCaptured()) {
return;
}
_lastMouseMove = usecTimestampNow();
if (_mouseHidden && showMouse && !OculusManager::isConnected() && !TV3DManager::isConnected()) {
getGLWidget()->setCursor(Qt::ArrowCursor);
_mouseHidden = false;
_seenMouseMove = true;
}
_mouseX = event->x();
_mouseY = event->y();
}
void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
if (!_aboutToQuit) {
_entities.mousePressEvent(event, deviceID);
}
_controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isMouseCaptured()) {
return;
}
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = event->x();
_mouseY = event->y();
_mouseDragStartedX = _mouseX;
_mouseDragStartedY = _mouseY;
_mousePressed = true;
if (_audio.mousePressEvent(_mouseX, _mouseY)) {
// stop propagation
return;
}
if (_rearMirrorTools->mousePressEvent(_mouseX, _mouseY)) {
// stop propagation
return;
}
// nobody handled this - make it an action event on the _window object
HFActionEvent actionEvent(HFActionEvent::startType(),
_myCamera.computePickRay(event->x(), event->y()));
sendEvent(this, &actionEvent);
} else if (event->button() == Qt::RightButton) {
// right click items here
}
}
}
void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
if (!_aboutToQuit) {
_entities.mouseReleaseEvent(event, deviceID);
}
_controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isMouseCaptured()) {
return;
}
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = event->x();
_mouseY = event->y();
_mousePressed = false;
checkBandwidthMeterClick();
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
// let's set horizontal offset to give stats some margin to mirror
int horizontalOffset = MIRROR_VIEW_WIDTH;
Stats::getInstance()->checkClick(_mouseX, _mouseY, _mouseDragStartedX, _mouseDragStartedY, horizontalOffset);
}
// fire an action end event
HFActionEvent actionEvent(HFActionEvent::endType(),
_myCamera.computePickRay(event->x(), event->y()));
sendEvent(this, &actionEvent);
}
}
}
void Application::touchUpdateEvent(QTouchEvent* event) {
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()) {
return;
}
bool validTouch = false;
if (activeWindow() == _window) {
const QList<QTouchEvent::TouchPoint>& tPoints = event->touchPoints();
_touchAvgX = 0.0f;
_touchAvgY = 0.0f;
int numTouches = tPoints.count();
if (numTouches > 1) {
for (int i = 0; i < numTouches; ++i) {
_touchAvgX += tPoints[i].pos().x();
_touchAvgY += tPoints[i].pos().y();
}
_touchAvgX /= (float)(numTouches);
_touchAvgY /= (float)(numTouches);
validTouch = true;
}
}
if (!_isTouchPressed) {
_touchDragStartedAvgX = _touchAvgX;
_touchDragStartedAvgY = _touchAvgY;
}
_isTouchPressed = validTouch;
}
void Application::touchBeginEvent(QTouchEvent* event) {
TouchEvent thisEvent(*event); // on touch begin, we don't compare to last event
_controllerScriptingInterface.emitTouchBeginEvent(thisEvent); // send events to any registered scripts
_lastTouchEvent = thisEvent; // and we reset our last event to this event before we call our update
touchUpdateEvent(event);
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isTouchCaptured()) {
return;
}
}
void Application::touchEndEvent(QTouchEvent* event) {
TouchEvent thisEvent(*event, _lastTouchEvent);
_controllerScriptingInterface.emitTouchEndEvent(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()) {
return;
}
// put any application specific touch behavior below here..
_touchDragStartedAvgX = _touchAvgX;
_touchDragStartedAvgY = _touchAvgY;
_isTouchPressed = false;
}
void Application::wheelEvent(QWheelEvent* event) {
_controllerScriptingInterface.emitWheelEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isWheelCaptured()) {
return;
}
}
void Application::dropEvent(QDropEvent *event) {
QString snapshotPath;
const QMimeData *mimeData = event->mimeData();
foreach (QUrl url, mimeData->urls()) {
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
snapshotPath = url.toLocalFile();
break;
}
}
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
if (snapshotData) {
if (!snapshotData->getDomain().isEmpty()) {
changeDomainHostname(snapshotData->getDomain());
}
_myAvatar->setPosition(snapshotData->getLocation());
_myAvatar->setOrientation(snapshotData->getOrientation());
} else {
QMessageBox msgBox;
msgBox.setText("No location details were found in this JPG, try dragging in an authentic Hifi snapshot.");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
}
void Application::sendPingPackets() {
QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
controlledBroadcastToNodes(pingPacket, NodeSet()
<< NodeType::VoxelServer << NodeType::EntityServer
<< NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::MetavoxelServer);
}
// Every second, check the frame rates and other stuff
void Application::timer() {
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
sendPingPackets();
}
float diffTime = (float)_timerStart.nsecsElapsed() / 1000000000.0f;
_fps = (float)_frameCount / diffTime;
_packetsPerSecond = (float) _datagramProcessor.getPacketCount() / diffTime;
_bytesPerSecond = (float) _datagramProcessor.getByteCount() / diffTime;
_frameCount = 0;
_datagramProcessor.resetCounters();
_timerStart.start();
// ask the node list to check in with the domain server
NodeList::getInstance()->sendDomainServerCheckIn();
}
void Application::idle() {
PerformanceTimer perfTimer("idle");
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing
// details normally.
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
// Only run simulation code if more than the targetFramePeriod have passed since last time we ran
double targetFramePeriod = 0.0;
unsigned int targetFramerate = getRenderTargetFramerate();
if (targetFramerate > 0) {
targetFramePeriod = 1000.0 / targetFramerate;
}
double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0;
if (timeSinceLastUpdate > targetFramePeriod) {
_lastTimeUpdated.start();
{
PerformanceTimer perfTimer("update");
PerformanceWarning warn(showWarnings, "Application::idle()... update()");
const float BIGGEST_DELTA_TIME_SECS = 0.25f;
update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS));
}
{
PerformanceTimer perfTimer("updateGL");
PerformanceWarning warn(showWarnings, "Application::idle()... updateGL()");
_glWidget->updateGL();
}
{
PerformanceTimer perfTimer("rest");
PerformanceWarning warn(showWarnings, "Application::idle()... rest of it");
_idleLoopStdev.addValue(timeSinceLastUpdate);
// Record standard deviation and reset counter if needed
const int STDEV_SAMPLES = 500;
if (_idleLoopStdev.getSamples() > STDEV_SAMPLES) {
_idleLoopMeasuredJitter = _idleLoopStdev.getStDev();
_idleLoopStdev.reset();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) {
PerformanceTimer perfTimer("buckyBalls");
_buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData());
}
// After finishing all of the above work, restart the idle timer, allowing 2ms to process events.
idleTimer->start(2);
if (_numChangedSettings > 0) {
saveSettings();
}
}
}
}
void Application::checkBandwidthMeterClick() {
// ... to be called upon button release
if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth) &&
glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY)))
<= BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH
&& _bandwidthMeter.isWithinArea(_mouseX, _mouseY, _glWidget->width(), _glWidget->height())) {
// The bandwidth meter is visible, the click didn't get dragged too far and
// we actually hit the bandwidth meter
Menu::getInstance()->bandwidthDetails();
}
}
void Application::setFullscreen(bool fullscreen) {
if (Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) {
if (fullscreen) {
// Menu show() after hide() doesn't work with Rift VR display so set height instead.
_window->menuBar()->setMaximumHeight(0);
} else {
_window->menuBar()->setMaximumHeight(QWIDGETSIZE_MAX);
}
}
_window->setWindowState(fullscreen ? (_window->windowState() | Qt::WindowFullScreen) :
(_window->windowState() & ~Qt::WindowFullScreen));
}
void Application::setEnable3DTVMode(bool enable3DTVMode) {
resizeGL(_glWidget->getDeviceWidth(),_glWidget->getDeviceHeight());
}
void Application::setEnableVRMode(bool enableVRMode) {
if (enableVRMode) {
if (!OculusManager::isConnected()) {
// attempt to reconnect the Oculus manager - it's possible this was a workaround
// for the sixense crash
OculusManager::disconnect();
OculusManager::connect();
}
OculusManager::recalibrate();
} else {
OculusManager::abandonCalibration();
}
resizeGL(_glWidget->getDeviceWidth(), _glWidget->getDeviceHeight());
}
void Application::setRenderVoxels(bool voxelRender) {
_voxelEditSender.setShouldSend(voxelRender);
if (!voxelRender) {
doKillLocalVoxels();
}
}
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
SixenseManager::getInstance().setLowVelocityFilter(lowVelocityFilter);
}
void Application::doKillLocalVoxels() {
_wantToKillLocalVoxels = true;
}
void Application::removeVoxel(glm::vec3 position,
float scale) {
VoxelDetail voxel;
voxel.x = position.x / TREE_SCALE;
voxel.y = position.y / TREE_SCALE;
voxel.z = position.z / TREE_SCALE;
voxel.s = scale / TREE_SCALE;
_voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, voxel);
// delete it locally to see the effect immediately (and in case no voxel server is present)
_voxels.getTree()->deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s);
}
void Application::makeVoxel(glm::vec3 position,
float scale,
unsigned char red,
unsigned char green,
unsigned char blue,
bool isDestructive) {
VoxelDetail voxel;
voxel.x = position.x / TREE_SCALE;
voxel.y = position.y / TREE_SCALE;
voxel.z = position.z / TREE_SCALE;
voxel.s = scale / TREE_SCALE;
voxel.red = red;
voxel.green = green;
voxel.blue = blue;
PacketType message = isDestructive ? PacketTypeVoxelSetDestructive : PacketTypeVoxelSet;
_voxelEditSender.sendVoxelEditMessage(message, voxel);
// create the voxel locally so it appears immediately
_voxels.getTree()->createVoxel(voxel.x, voxel.y, voxel.z, voxel.s,
voxel.red, voxel.green, voxel.blue,
isDestructive);
}
glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVoxel) {
return glm::vec3((mouseVoxel.x + mouseVoxel.s / 2.f) * TREE_SCALE, (mouseVoxel.y + mouseVoxel.s / 2.f) * TREE_SCALE,
(mouseVoxel.z + mouseVoxel.s / 2.f) * TREE_SCALE);
}
FaceTracker* Application::getActiveFaceTracker() {
return (_dde.isActive() ? static_cast<FaceTracker*>(&_dde) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)));
}
struct SendVoxelsOperationArgs {
const unsigned char* newBaseOctCode;
};
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) {
QVector<EntityItem*> entities;
_entities.getTree()->findEntities(AACube(glm::vec3(x / (float)TREE_SCALE,
y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), entities);
if (entities.size() > 0) {
glm::vec3 root(x, y, z);
EntityTree exportTree;
for (int i = 0; i < entities.size(); i++) {
EntityItemProperties properties = entities.at(i)->getProperties();
EntityItemID id = entities.at(i)->getEntityItemID();
properties.setPosition(properties.getPosition() - root);
exportTree.addEntity(id, properties);
}
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
} else {
qDebug() << "No models were selected";
return false;
}
// restore the main window's active state
_window->activateWindow();
return true;
}
bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData;
if (voxel->isColored()) {
const unsigned char* nodeOctalCode = voxel->getOctalCode();
unsigned char* codeColorBuffer = NULL;
int codeLength = 0;
int bytesInCode = 0;
int codeAndColorLength;
// If the newBase is NULL, then don't rebase
if (args->newBaseOctCode) {
codeColorBuffer = rebaseOctalCode(nodeOctalCode, args->newBaseOctCode, true);
codeLength = numberOfThreeBitSectionsInCode(codeColorBuffer);
bytesInCode = bytesRequiredForCodeLength(codeLength);
codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA;
} else {
codeLength = numberOfThreeBitSectionsInCode(nodeOctalCode);
bytesInCode = bytesRequiredForCodeLength(codeLength);
codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA;
codeColorBuffer = new unsigned char[codeAndColorLength];
memcpy(codeColorBuffer, nodeOctalCode, bytesInCode);
}
// copy the colors over
codeColorBuffer[bytesInCode + RED_INDEX] = voxel->getColor()[RED_INDEX];
codeColorBuffer[bytesInCode + GREEN_INDEX] = voxel->getColor()[GREEN_INDEX];
codeColorBuffer[bytesInCode + BLUE_INDEX] = voxel->getColor()[BLUE_INDEX];
getInstance()->_voxelEditSender.queueVoxelEditMessage(PacketTypeVoxelSetDestructive,
codeColorBuffer, codeAndColorLength);
delete[] codeColorBuffer;
}
return true; // keep going
}
void Application::exportVoxels(const VoxelDetail& sourceVoxel) {
QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
QString suggestedName = desktopLocation.append("/voxels.svo");
QString fileNameString = QFileDialog::getSaveFileName(_glWidget, tr("Export Voxels"), suggestedName,
tr("Sparse Voxel Octree Files (*.svo)"));
QByteArray fileNameAscii = fileNameString.toLocal8Bit();
const char* fileName = fileNameAscii.data();
VoxelTreeElement* selectedNode = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s);
if (selectedNode) {
VoxelTree exportTree;
getVoxelTree()->copySubTreeIntoNewTree(selectedNode, &exportTree, true);
exportTree.writeToSVOFile(fileName);
}
// restore the main window's active state
_window->activateWindow();
}
void Application::importVoxels() {
_importSucceded = false;
if (!_voxelImportDialog) {
_voxelImportDialog = new VoxelImportDialog(_window);
_voxelImportDialog->loadSettings(_settings);
}
if (!_voxelImportDialog->exec()) {
qDebug() << "Import succeeded." << endl;
_importSucceded = true;
} else {
qDebug() << "Import failed." << endl;
if (_sharedVoxelSystem.getTree() == _voxelImporter.getVoxelTree()) {
_sharedVoxelSystem.killLocalVoxels();
_sharedVoxelSystem.changeTree(&_clipboard);
}
}
// restore the main window's active state
_window->activateWindow();
emit importDone();
}
bool Application::importEntities(const QString& filename) {
_entityClipboard.eraseAllOctreeElements();
bool success = _entityClipboard.readFromSVOFile(filename.toLocal8Bit().constData());
if (success) {
_entityClipboard.reaverageOctreeElements();
}
return success;
}
void Application::pasteEntities(float x, float y, float z) {
_entityClipboard.sendEntities(&_entityEditSender, _entities.getTree(), x, y, z);
}
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
copyVoxels(sourceVoxel);
deleteVoxelAt(sourceVoxel);
}
void Application::copyVoxels(const VoxelDetail& sourceVoxel) {
// switch to and clear the clipboard first...
_sharedVoxelSystem.killLocalVoxels();
if (_sharedVoxelSystem.getTree() != &_clipboard) {
_clipboard.eraseAllOctreeElements();
_sharedVoxelSystem.changeTree(&_clipboard);
}
// then copy onto it if there is something to copy
VoxelTreeElement* selectedNode = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s);
if (selectedNode) {
getVoxelTree()->copySubTreeIntoNewTree(selectedNode, _sharedVoxelSystem.getTree(), true);
_sharedVoxelSystem.forceRedrawEntireTree();
}
}
void Application::pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination) {
// Recurse the clipboard tree, where everything is root relative, and send all the colored voxels to
// the server as an set voxel message, this will also rebase the voxels to the new location
SendVoxelsOperationArgs args;
args.newBaseOctCode = octalCodeDestination;
_sharedVoxelSystem.getTree()->recurseTreeWithOperation(sendVoxelsOperation, &args);
// Switch back to clipboard if it was an import
if (_sharedVoxelSystem.getTree() != &_clipboard) {
_sharedVoxelSystem.killLocalVoxels();
_sharedVoxelSystem.changeTree(&_clipboard);
}
_voxelEditSender.releaseQueuedMessages();
}
void Application::pasteVoxels(const VoxelDetail& sourceVoxel) {
unsigned char* calculatedOctCode = NULL;
VoxelTreeElement* selectedNode = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s);
// we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the
// voxel size/position details. If we don't have an actual selectedNode then use the mouseVoxel to create a
// target octalCode for where the user is pointing.
const unsigned char* octalCodeDestination;
if (selectedNode) {
octalCodeDestination = selectedNode->getOctalCode();
} else {
octalCodeDestination = calculatedOctCode = pointToVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s);
}
pasteVoxelsToOctalCode(octalCodeDestination);
if (calculatedOctCode) {
delete[] calculatedOctCode;
}
}
void Application::nudgeVoxelsByVector(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec) {
VoxelTreeElement* nodeToNudge = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s);
if (nodeToNudge) {
_voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender);
}
}
void Application::initDisplay() {
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE);
glShadeModel(GL_SMOOTH);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
}
void Application::init() {
_sharedVoxelSystemViewFrustum.setPosition(glm::vec3(TREE_SCALE / 2.0f,
TREE_SCALE / 2.0f,
3.0f * TREE_SCALE / 2.0f));
_sharedVoxelSystemViewFrustum.setNearClip(TREE_SCALE / 2.0f);
_sharedVoxelSystemViewFrustum.setFarClip(3.0f * TREE_SCALE / 2.0f);
_sharedVoxelSystemViewFrustum.setFieldOfView(90.f);
_sharedVoxelSystemViewFrustum.setOrientation(glm::quat());
_sharedVoxelSystemViewFrustum.calculate();
_sharedVoxelSystem.setViewFrustum(&_sharedVoxelSystemViewFrustum);
VoxelTreeElement::removeUpdateHook(&_sharedVoxelSystem);
// Cleanup of the original shared tree
_sharedVoxelSystem.init();
_voxelImportDialog = new VoxelImportDialog(_window);
_environment.init();
_deferredLightingEffect.init();
_glowEffect.init();
_ambientOcclusionEffect.init();
_voxelShader.init();
_pointShader.init();
_mouseX = _glWidget->width() / 2;
_mouseY = _glWidget->height() / 2;
QCursor::setPos(_mouseX, _mouseY);
// TODO: move _myAvatar out of Application. Move relevant code to MyAvataar or AvatarManager
_avatarManager.init();
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_mirrorCamera.setMode(CAMERA_MODE_MIRROR);
OculusManager::connect();
if (OculusManager::isConnected()) {
QMetaObject::invokeMethod(Menu::getInstance()->getActionForOption(MenuOption::Fullscreen),
"trigger",
Qt::QueuedConnection);
}
TV3DManager::connect();
if (TV3DManager::isConnected()) {
QMetaObject::invokeMethod(Menu::getInstance()->getActionForOption(MenuOption::Fullscreen),
"trigger",
Qt::QueuedConnection);
}
_timerStart.start();
_lastTimeUpdated.start();
Menu::getInstance()->loadSettings();
_audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings());
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
if (urlIndex != -1) {
AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1));
} else {
// check if we have a URL in settings to load to jump back to
// we load this separate from the other settings so we don't double lookup a URL
QSettings* interfaceSettings = lockSettings();
QUrl addressURL = interfaceSettings->value(SETTINGS_ADDRESS_KEY).toUrl();
AddressManager::getInstance().handleLookupString(addressURL.toString());
unlockSettings();
}
qDebug() << "Loaded settings";
#ifdef __APPLE__
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) {
// on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash
// if hydra support is temporarily not required
Menu::getInstance()->toggleSixense(true);
}
#else
// setup sixense
Menu::getInstance()->toggleSixense(true);
#endif
// initialize our face trackers after loading the menu settings
_faceshift.init();
_visage.init();
Leapmotion::init();
// fire off an immediate domain-server check in now that settings are loaded
NodeList::getInstance()->sendDomainServerCheckIn();
// Set up VoxelSystem after loading preferences so we can get the desired max voxel count
_voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels());
_voxels.setUseVoxelShader(false);
_voxels.setVoxelsAsPoints(false);
_voxels.setDisableFastVoxelPipeline(false);
_voxels.init();
_entities.init();
_entities.setViewFrustum(getViewFrustum());
_entityCollisionSystem.init(&_entityEditSender, _entities.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
// connect the _entityCollisionSystem to our script engine's EntityScriptingInterface
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithVoxel,
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithVoxel);
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
// of events related clicking, hovering over, and entering entities
_entities.connectSignalsToSlots(ScriptEngine::getEntityScriptingInterface());
_entityClipboardRenderer.init();
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
_entityClipboardRenderer.setTree(&_entityClipboard);
_metavoxels.init();
_audio.init(_glWidget);
_rearMirrorTools = new RearMirrorTools(_glWidget, _mirrorViewRect, _settings);
connect(_rearMirrorTools, SIGNAL(closeView()), SLOT(closeMirrorView()));
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
// set up our audio reflector
_audioReflector.setMyAvatar(getAvatar());
_audioReflector.setVoxels(_voxels.getTree());
_audioReflector.setAudio(getAudio());
_audioReflector.setAvatarManager(&_avatarManager);
connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection);
connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection);
connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector,
&AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection);
connect(getAudio(), &Audio::muteToggled, AudioDeviceScriptingInterface::getInstance(),
&AudioDeviceScriptingInterface::muteToggled, Qt::DirectConnection);
// save settings when avatar changes
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
}
void Application::closeMirrorView() {
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
Menu::getInstance()->triggerOption(MenuOption::Mirror);;
}
}
void Application::restoreMirrorView() {
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
Menu::getInstance()->triggerOption(MenuOption::Mirror);;
}
if (!Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
Menu::getInstance()->triggerOption(MenuOption::FullscreenMirror);
}
}
void Application::shrinkMirrorView() {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
Menu::getInstance()->triggerOption(MenuOption::Mirror);;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
Menu::getInstance()->triggerOption(MenuOption::FullscreenMirror);
}
}
const float HEAD_SPHERE_RADIUS = 0.1f;
bool Application::isLookingAtMyAvatar(Avatar* avatar) {
glm::vec3 theirLookAt = avatar->getHead()->getLookAtPosition();
glm::vec3 myEyePosition = _myAvatar->getHead()->getEyePosition();
if (pointInSphere(theirLookAt, myEyePosition, HEAD_SPHERE_RADIUS * _myAvatar->getScale())) {
return true;
}
return false;
}
void Application::updateLOD() {
PerformanceTimer perfTimer("LOD");
// adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) {
Menu::getInstance()->autoAdjustLOD(_fps);
} else {
Menu::getInstance()->resetLODAdjust();
}
}
void Application::updateMouseRay() {
PerformanceTimer perfTimer("mouseRay");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMouseRay()");
// make sure the frustum is up-to-date
loadViewFrustum(_myCamera, _viewFrustum);
// if the mouse pointer isn't visible, act like it's at the center of the screen
float x = 0.5f, y = 0.5f;
if (!_mouseHidden) {
x = _mouseX / (float)_glWidget->width();
y = _mouseY / (float)_glWidget->height();
}
_viewFrustum.computePickRay(x, y, _mouseRayOrigin, _mouseRayDirection);
// adjust for mirroring
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
glm::vec3 mouseRayOffset = _mouseRayOrigin - _viewFrustum.getPosition();
_mouseRayOrigin -= 2.0f * (_viewFrustum.getDirection() * glm::dot(_viewFrustum.getDirection(), mouseRayOffset) +
_viewFrustum.getRight() * glm::dot(_viewFrustum.getRight(), mouseRayOffset));
_mouseRayDirection -= 2.0f * (_viewFrustum.getDirection() * glm::dot(_viewFrustum.getDirection(), _mouseRayDirection) +
_viewFrustum.getRight() * glm::dot(_viewFrustum.getRight(), _mouseRayDirection));
}
// tell my avatar if the mouse is being pressed...
_myAvatar->setMousePressed(_mousePressed);
// tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position
_myAvatar->setMouseRay(_mouseRayOrigin, _mouseRayDirection);
}
void Application::updateFaceshift() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateFaceshift()");
// Update faceshift
_faceshift.update();
// Copy angular velocity if measured by faceshift, to the head
if (_faceshift.isActive()) {
_myAvatar->getHead()->setAngularVelocity(_faceshift.getHeadAngularVelocity());
}
}
void Application::updateVisage() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateVisage()");
// Update Visage
_visage.update();
}
void Application::updateDDE() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateDDE()");
// Update Cara
_dde.update();
}
void Application::updateMyAvatarLookAtPosition() {
PerformanceTimer perfTimer("lookAt");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
_myAvatar->updateLookAtTargetAvatar();
FaceTracker* tracker = getActiveFaceTracker();
bool isLookingAtSomeone = false;
glm::vec3 lookAtSpot;
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
// When I am in mirror mode, just look right at the camera (myself)
if (!OculusManager::isConnected()) {
lookAtSpot = _myCamera.getPosition();
} else {
if (_myAvatar->isLookingAtLeftEye()) {
lookAtSpot = OculusManager::getLeftEyePosition();
} else {
lookAtSpot = OculusManager::getRightEyePosition();
}
}
} else {
AvatarSharedPointer lookingAt = _myAvatar->getLookAtTargetAvatar().toStrongRef();
if (lookingAt && _myAvatar != lookingAt.data()) {
isLookingAtSomeone = true;
// If I am looking at someone else, look directly at one of their eyes
if (tracker) {
// If a face tracker is active, look at the eye for the side my gaze is biased toward
if (tracker->getEstimatedEyeYaw() > _myAvatar->getHead()->getFinalYaw()) {
// Look at their right eye
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getRightEyePosition();
} else {
// Look at their left eye
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getLeftEyePosition();
}
} else {
// Need to add randomly looking back and forth between left and right eye for case with no tracker
if (_myAvatar->isLookingAtLeftEye()) {
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getLeftEyePosition();
} else {
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getRightEyePosition();
}
}
} else {
// I am not looking at anyone else, so just look forward
lookAtSpot = _myAvatar->getHead()->getEyePosition() +
(_myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.f, 0.f, -TREE_SCALE));
}
}
//
// Deflect the eyes a bit to match the detected Gaze from 3D camera if active
//
if (tracker) {
float eyePitch = tracker->getEstimatedEyePitch();
float eyeYaw = tracker->getEstimatedEyeYaw();
const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f;
// deflect using Faceshift gaze data
glm::vec3 origin = _myAvatar->getHead()->getEyePosition();
float pitchSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? -1.0f : 1.0f;
float deflection = Menu::getInstance()->getFaceshiftEyeDeflection();
if (isLookingAtSomeone) {
deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT;
}
lookAtSpot = origin + _myCamera.getRotation() * glm::quat(glm::radians(glm::vec3(
eyePitch * pitchSign * deflection, eyeYaw * deflection, 0.0f))) *
glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin);
}
_myAvatar->getHead()->setLookAtPosition(lookAtSpot);
}
void Application::updateThreads(float deltaTime) {
PerformanceTimer perfTimer("updateThreads");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateThreads()");
// parse voxel packets
if (!_enableProcessVoxelsThread) {
_octreeProcessor.threadRoutine();
_voxelHideShowThread.threadRoutine();
_voxelEditSender.threadRoutine();
_entityEditSender.threadRoutine();
}
}
void Application::updateMetavoxels(float deltaTime) {
PerformanceTimer perfTimer("updateMetavoxels");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()");
if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) {
_metavoxels.simulate(deltaTime);
}
}
void Application::cameraMenuChanged() {
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
_myCamera.setMode(CAMERA_MODE_MIRROR);
}
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
}
} else {
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
}
}
}
void Application::updateCamera(float deltaTime) {
PerformanceTimer perfTimer("updateCamera");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateCamera()");
if (!OculusManager::isConnected() && !TV3DManager::isConnected() &&
Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) {
FaceTracker* tracker = getActiveFaceTracker();
if (tracker) {
const float EYE_OFFSET_SCALE = 0.025f;
glm::vec3 position = tracker->getHeadTranslation() * EYE_OFFSET_SCALE;
float xSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? 1.0f : -1.0f;
_myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, -position.z));
updateProjectionMatrix();
}
}
}
void Application::updateDialogs(float deltaTime) {
PerformanceTimer perfTimer("updateDialogs");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateDialogs()");
// Update bandwidth dialog, if any
BandwidthDialog* bandwidthDialog = Menu::getInstance()->getBandwidthDialog();
if (bandwidthDialog) {
bandwidthDialog->update();
}
OctreeStatsDialog* octreeStatsDialog = Menu::getInstance()->getOctreeStatsDialog();
if (octreeStatsDialog) {
octreeStatsDialog->update();
}
}
void Application::updateCursor(float deltaTime) {
PerformanceTimer perfTimer("updateCursor");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateCursor()");
// watch mouse position, if it hasn't moved, hide the cursor
bool underMouse = _glWidget->underMouse();
if (!_mouseHidden) {
quint64 now = usecTimestampNow();
int elapsed = now - _lastMouseMove;
const int HIDE_CURSOR_TIMEOUT = 1 * 1000 * 1000; // 1 second
if (elapsed > HIDE_CURSOR_TIMEOUT && (underMouse || !_seenMouseMove)) {
getGLWidget()->setCursor(Qt::BlankCursor);
_mouseHidden = true;
}
} else {
// if the mouse is hidden, but we're not inside our window, then consider ourselves to be moving
if (!underMouse && _seenMouseMove) {
_lastMouseMove = usecTimestampNow();
getGLWidget()->setCursor(Qt::ArrowCursor);
_mouseHidden = false;
}
}
}
void Application::update(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::update()");
updateLOD();
updateMouseRay(); // check what's under the mouse and update the mouse voxel
{
PerformanceTimer perfTimer("devices");
DeviceTracker::updateAll();
updateFaceshift();
updateVisage();
SixenseManager::getInstance().update(deltaTime);
JoystickScriptingInterface::getInstance().update();
_prioVR.update(deltaTime);
}
// Dispatch input events
_controllerScriptingInterface.updateInputControllers();
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
updateMetavoxels(deltaTime); // update metavoxels
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
updateDialogs(deltaTime); // update various stats dialogs if present
updateCursor(deltaTime); // Handle cursor updates
{
PerformanceTimer perfTimer("entities");
_entities.update(); // update the models...
{
PerformanceTimer perfTimer("collisions");
_entityCollisionSystem.update(); // collide the entities...
}
}
{
PerformanceTimer perfTimer("overlays");
_overlays.update(deltaTime);
}
{
PerformanceTimer perfTimer("myAvatar");
updateMyAvatarLookAtPosition();
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
}
{
PerformanceTimer perfTimer("emitSimulating");
// let external parties know we're updating
emit simulating(deltaTime);
}
// Update _viewFrustum with latest camera and view frustum data...
// NOTE: we get this from the view frustum, to make it simpler, since the
// loadViewFrumstum() method will get the correct details from the camera
// We could optimize this to not actually load the viewFrustum, since we don't
// actually need to calculate the view frustum planes to send these details
// to the server.
{
PerformanceTimer perfTimer("loadViewFrustum");
loadViewFrustum(_myCamera, _viewFrustum);
}
quint64 now = usecTimestampNow();
// Update my voxel servers with my current voxel query...
{
PerformanceTimer perfTimer("queryOctree");
quint64 sinceLastQuery = now - _lastQueriedTime;
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum);
// if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
if (queryIsDue || viewIsDifferentEnough) {
_lastQueriedTime = now;
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
queryOctree(NodeType::EntityServer, PacketTypeEntityQuery, _entityServerJurisdictions);
}
_lastQueriedViewFrustum = _viewFrustum;
}
}
// sent nack packets containing missing sequence numbers of received packets from nodes
{
quint64 sinceLastNack = now - _lastNackTime;
const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND;
if (sinceLastNack > TOO_LONG_SINCE_LAST_NACK) {
_lastNackTime = now;
sendNackPackets();
}
}
// send packet containing downstream audio stats to the AudioMixer
{
quint64 sinceLastNack = now - _lastSendDownstreamAudioStats;
if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) {
_lastSendDownstreamAudioStats = now;
QMetaObject::invokeMethod(&_audio, "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
}
}
}
void Application::updateMyAvatar(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMyAvatar()");
_myAvatar->update(deltaTime);
{
// send head/hand data to the avatar mixer and voxel server
PerformanceTimer perfTimer("send");
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
packet.append(_myAvatar->toByteArray());
controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);
}
}
int Application::sendNackPackets() {
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
return 0;
}
int packetsSent = 0;
char packet[MAX_PACKET_SIZE];
// iterates thru all nodes in NodeList
foreach(const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getActiveSocket() &&
( node->getType() == NodeType::VoxelServer
|| node->getType() == NodeType::EntityServer)
) {
QUuid nodeUUID = node->getUUID();
// if there are octree packets from this node that are waiting to be processed,
// don't send a NACK since the missing packets may be among those waiting packets.
if (_octreeProcessor.hasPacketsToProcessFrom(nodeUUID)) {
continue;
}
_octreeSceneStatsLock.lockForRead();
// retreive octree scene stats of this node
if (_octreeServerSceneStats.find(nodeUUID) == _octreeServerSceneStats.end()) {
_octreeSceneStatsLock.unlock();
continue;
}
// get sequence number stats of node, prune its missing set, and make a copy of the missing set
SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats();
sequenceNumberStats.pruneMissingSet();
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers = sequenceNumberStats.getMissingSet();
_octreeSceneStatsLock.unlock();
// construct nack packet(s) for this node
int numSequenceNumbersAvailable = missingSequenceNumbers.size();
QSet<OCTREE_PACKET_SEQUENCE>::const_iterator missingSequenceNumbersIterator = missingSequenceNumbers.constBegin();
while (numSequenceNumbersAvailable > 0) {
char* dataAt = packet;
int bytesRemaining = MAX_PACKET_SIZE;
// pack header
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeOctreeDataNack);
dataAt += numBytesPacketHeader;
bytesRemaining -= numBytesPacketHeader;
// calculate and pack the number of sequence numbers
int numSequenceNumbersRoomFor = (bytesRemaining - sizeof(uint16_t)) / sizeof(OCTREE_PACKET_SEQUENCE);
uint16_t numSequenceNumbers = min(numSequenceNumbersAvailable, numSequenceNumbersRoomFor);
uint16_t* numSequenceNumbersAt = (uint16_t*)dataAt;
*numSequenceNumbersAt = numSequenceNumbers;
dataAt += sizeof(uint16_t);
// pack sequence numbers
for (int i = 0; i < numSequenceNumbers; i++) {
OCTREE_PACKET_SEQUENCE* sequenceNumberAt = (OCTREE_PACKET_SEQUENCE*)dataAt;
*sequenceNumberAt = *missingSequenceNumbersIterator;
dataAt += sizeof(OCTREE_PACKET_SEQUENCE);
missingSequenceNumbersIterator++;
}
numSequenceNumbersAvailable -= numSequenceNumbers;
// send it
NodeList::getInstance()->writeUnverifiedDatagram(packet, dataAt - packet, node);
packetsSent++;
}
}
}
return packetsSent;
}
void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) {
//qDebug() << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView();
bool wantExtraDebugging = getLogger()->extraDebugging();
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
_octreeQuery.setWantLowResMoving(true);
_octreeQuery.setWantColor(true);
_octreeQuery.setWantDelta(true);
_octreeQuery.setWantOcclusionCulling(false);
_octreeQuery.setWantCompression(true);
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
_octreeQuery.setCameraFov(_viewFrustum.getFieldOfView());
_octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio());
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(_viewFrustum.getEyeOffsetPosition());
_octreeQuery.setOctreeSizeScale(Menu::getInstance()->getVoxelSizeScale());
_octreeQuery.setBoundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust());
unsigned char queryPacket[MAX_PACKET_SIZE];
// Iterate all of the nodes, and get a count of how many voxel servers we have...
int totalServers = 0;
int inViewServers = 0;
int unknownJurisdictionServers = 0;
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
// only send to the NodeTypes that are serverType
if (node->getActiveSocket() && node->getType() == serverType) {
totalServers++;
// get the server bounds for this server
QUuid nodeUUID = node->getUUID();
// if we haven't heard from this voxel server, go ahead and send it a query, so we
// can get the jurisdiction...
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
unknownJurisdictionServers++;
} else {
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
if (rootCode) {
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inViewServers++;
}
}
}
}
}
if (wantExtraDebugging) {
qDebug("Servers: total %d, in view %d, unknown jurisdiction %d",
totalServers, inViewServers, unknownJurisdictionServers);
}
int perServerPPS = 0;
const int SMALL_BUDGET = 10;
int perUnknownServer = SMALL_BUDGET;
int totalPPS = Menu::getInstance()->getMaxVoxelPacketsPerSecond();
// determine PPS based on number of servers
if (inViewServers >= 1) {
// set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS
// for each unknown jurisdiction server
perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer);
} else {
if (unknownJurisdictionServers > 0) {
perUnknownServer = (totalPPS / unknownJurisdictionServers);
}
}
if (wantExtraDebugging) {
qDebug("perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer);
}
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
// only send to the NodeTypes that are serverType
if (node->getActiveSocket() && node->getType() == serverType) {
// get the server bounds for this server
QUuid nodeUUID = node->getUUID();
bool inView = false;
bool unknownView = false;
// if we haven't heard from this voxel server, go ahead and send it a query, so we
// can get the jurisdiction...
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
unknownView = true; // assume it's in view
if (wantExtraDebugging) {
qDebug() << "no known jurisdiction for node " << *node << ", assume it's visible.";
}
} else {
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
if (rootCode) {
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
}
} else {
if (wantExtraDebugging) {
qDebug() << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
}
}
}
if (inView) {
_octreeQuery.setMaxOctreePacketsPerSecond(perServerPPS);
} else if (unknownView) {
if (wantExtraDebugging) {
qDebug() << "no known jurisdiction for node " << *node << ", give it budget of "
<< perUnknownServer << " to send us jurisdiction.";
}
// set the query's position/orientation to be degenerate in a manner that will get the scene quickly
// If there's only one server, then don't do this, and just let the normal voxel query pass through
// as expected... this way, we will actually get a valid scene if there is one to be seen
if (totalServers > 1) {
_octreeQuery.setCameraPosition(glm::vec3(-0.1,-0.1,-0.1));
const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0);
_octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE);
_octreeQuery.setCameraNearClip(0.1f);
_octreeQuery.setCameraFarClip(0.1f);
if (wantExtraDebugging) {
qDebug() << "Using 'minimal' camera position for node" << *node;
}
} else {
if (wantExtraDebugging) {
qDebug() << "Using regular camera position for node" << *node;
}
}
_octreeQuery.setMaxOctreePacketsPerSecond(perUnknownServer);
} else {
_octreeQuery.setMaxOctreePacketsPerSecond(0);
}
// set up the packet for sending...
unsigned char* endOfQueryPacket = queryPacket;
// insert packet type/version and node UUID
endOfQueryPacket += populatePacketHeader(reinterpret_cast<char*>(endOfQueryPacket), packetType);
// encode the query data...
endOfQueryPacket += _octreeQuery.getBroadcastData(endOfQueryPacket);
int packetLength = endOfQueryPacket - queryPacket;
// make sure we still have an active socket
nodeList->writeUnverifiedDatagram(reinterpret_cast<const char*>(queryPacket), packetLength, node);
// Feed number of bytes to corresponding channel of the bandwidth meter
_bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(packetLength);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// loadViewFrustum()
//
// Description: this will load the view frustum bounds for EITHER the head
// or the "myCamera".
//
void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) {
// We will use these below, from either the camera or head vectors calculated above
glm::vec3 position(camera.getPosition());
float fov = camera.getFieldOfView(); // degrees
float nearClip = camera.getNearClip();
float farClip = camera.getFarClip();
float aspectRatio = camera.getAspectRatio();
glm::quat rotation = camera.getRotation();
// Set the viewFrustum up with the correct position and orientation of the camera
viewFrustum.setPosition(position);
viewFrustum.setOrientation(rotation);
// Also make sure it's got the correct lens details from the camera
viewFrustum.setAspectRatio(aspectRatio);
viewFrustum.setFieldOfView(fov); // degrees
viewFrustum.setNearClip(nearClip);
viewFrustum.setFarClip(farClip);
viewFrustum.setEyeOffsetPosition(camera.getEyeOffsetPosition());
viewFrustum.setEyeOffsetOrientation(camera.getEyeOffsetOrientation());
// Ask the ViewFrustum class to calculate our corners
viewFrustum.calculate();
}
glm::vec3 Application::getSunDirection() {
return glm::normalize(_environment.getClosestData(_myCamera.getPosition()).getSunLocation(_myCamera.getPosition()) -
_myCamera.getPosition());
}
void Application::updateShadowMap() {
PerformanceTimer perfTimer("shadowMap");
QOpenGLFramebufferObject* fbo = _textureCache.getShadowFramebufferObject();
fbo->bind();
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::vec3 lightDirection = -getSunDirection();
glm::quat rotation = rotationBetween(IDENTITY_FRONT, lightDirection);
glm::quat inverseRotation = glm::inverse(rotation);
const float SHADOW_MATRIX_DISTANCES[] = { 0.0f, 2.0f, 6.0f, 14.0f, 30.0f };
const glm::vec2 MAP_COORDS[] = { glm::vec2(0.0f, 0.0f), glm::vec2(0.5f, 0.0f),
glm::vec2(0.0f, 0.5f), glm::vec2(0.5f, 0.5f) };
float frustumScale = 1.0f / (_viewFrustum.getFarClip() - _viewFrustum.getNearClip());
loadViewFrustum(_myCamera, _viewFrustum);
int matrixCount = 1;
int targetSize = fbo->width();
float targetScale = 1.0f;
if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) {
matrixCount = CASCADED_SHADOW_MATRIX_COUNT;
targetSize = fbo->width() / 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);
float nearScale = SHADOW_MATRIX_DISTANCES[i] * frustumScale;
float farScale = SHADOW_MATRIX_DISTANCES[i + 1] * frustumScale;
glm::vec3 points[] = {
glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), nearScale),
glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), nearScale),
glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), nearScale),
glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), nearScale),
glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), farScale),
glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), farScale),
glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), farScale),
glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), farScale) };
glm::vec3 center;
for (size_t j = 0; j < sizeof(points) / sizeof(points[0]); j++) {
center += points[j];
}
center /= (float)(sizeof(points) / sizeof(points[0]));
float radius = 0.0f;
for (size_t j = 0; j < sizeof(points) / sizeof(points[0]); j++) {
radius = qMax(radius, glm::distance(points[j], center));
}
if (i < 3) {
const float RADIUS_SCALE = 0.5f;
_shadowDistances[i] = -glm::distance(_viewFrustum.getPosition(), center) - radius * RADIUS_SCALE;
}
center = inverseRotation * center;
// to reduce texture "shimmer," move in texel increments
float texelSize = (2.0f * radius) / targetSize;
center = glm::vec3(roundf(center.x / texelSize) * texelSize, roundf(center.y / texelSize) * texelSize,
roundf(center.z / texelSize) * texelSize);
glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius);
glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius);
// stretch out our extents in z so that we get all of the avatars
minima.z -= _viewFrustum.getFarClip() * 0.5f;
maxima.z += _viewFrustum.getFarClip() * 0.5f;
// save the combined matrix for rendering
_shadowMatrices[i] = glm::transpose(glm::translate(glm::vec3(coord, 0.0f)) *
glm::scale(glm::vec3(targetScale, targetScale, 1.0f)) *
glm::translate(glm::vec3(0.5f, 0.5f, 0.5f)) * glm::scale(glm::vec3(0.5f, 0.5f, 0.5f)) *
glm::ortho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z) * glm::mat4_cast(inverseRotation));
// update the shadow view frustum
_shadowViewFrustum.setPosition(rotation * ((minima + maxima) * 0.5f));
_shadowViewFrustum.setOrientation(rotation);
_shadowViewFrustum.setOrthographic(true);
_shadowViewFrustum.setWidth(maxima.x - minima.x);
_shadowViewFrustum.setHeight(maxima.y - minima.y);
_shadowViewFrustum.setNearClip(minima.z);
_shadowViewFrustum.setFarClip(maxima.z);
_shadowViewFrustum.setEyeOffsetPosition(glm::vec3());
_shadowViewFrustum.setEyeOffsetOrientation(glm::quat());
_shadowViewFrustum.calculate();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glm::vec3 axis = glm::axis(inverseRotation);
glRotatef(glm::degrees(glm::angle(inverseRotation)), axis.x, axis.y, axis.z);
// store view matrix without translation, which we'll use for precision-sensitive objects
updateUntranslatedViewMatrix();
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt
{
PerformanceTimer perfTimer("avatarManager");
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
}
{
PerformanceTimer perfTimer("entities");
_entities.render(RenderArgs::SHADOW_RENDER_MODE);
}
// render JS/scriptable overlays
{
PerformanceTimer perfTimer("3dOverlays");
_overlays.render3D(RenderArgs::SHADOW_RENDER_MODE);
}
glDisable(GL_POLYGON_OFFSET_FILL);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
}
fbo->release();
glViewport(0, 0, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight());
}
const GLfloat WORLD_AMBIENT_COLOR[] = { 0.525f, 0.525f, 0.6f };
const GLfloat WORLD_DIFFUSE_COLOR[] = { 0.6f, 0.525f, 0.525f };
const GLfloat WORLD_SPECULAR_COLOR[] = { 0.94f, 0.94f, 0.737f, 1.0f };
void Application::setupWorldLight() {
// Setup 3D lights (after the camera transform, so that they are positioned in world space)
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glm::vec3 sunDirection = getSunDirection();
GLfloat light_position0[] = { sunDirection.x, sunDirection.y, sunDirection.z, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
glLightfv(GL_LIGHT0, GL_AMBIENT, WORLD_AMBIENT_COLOR);
glLightfv(GL_LIGHT0, GL_DIFFUSE, WORLD_DIFFUSE_COLOR);
glLightfv(GL_LIGHT0, GL_SPECULAR, WORLD_SPECULAR_COLOR);
glMaterialfv(GL_FRONT, GL_SPECULAR, WORLD_SPECULAR_COLOR);
glMateriali(GL_FRONT, GL_SHININESS, 96);
}
QImage Application::renderAvatarBillboard() {
_textureCache.getPrimaryFramebufferObject()->bind();
// the "glow" here causes an alpha of one
Glower glower;
const int BILLBOARD_SIZE = 64;
renderRearViewMirror(QRect(0, _glWidget->getDeviceHeight() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true);
QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32);
glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
_textureCache.getPrimaryFramebufferObject()->release();
return image;
}
void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("display");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");
// transform by eye offset
// load the view frustum
loadViewFrustum(whichCamera, _displayViewFrustum);
// flip x if in mirror mode (also requires reversing winding order for backface culling)
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
glScalef(-1.0f, 1.0f, 1.0f);
glFrontFace(GL_CW);
} else {
glFrontFace(GL_CCW);
}
glm::vec3 eyeOffsetPos = whichCamera.getEyeOffsetPosition();
glm::quat eyeOffsetOrient = whichCamera.getEyeOffsetOrientation();
glm::vec3 eyeOffsetAxis = glm::axis(eyeOffsetOrient);
glRotatef(-glm::degrees(glm::angle(eyeOffsetOrient)), eyeOffsetAxis.x, eyeOffsetAxis.y, eyeOffsetAxis.z);
glTranslatef(-eyeOffsetPos.x, -eyeOffsetPos.y, -eyeOffsetPos.z);
// transform view according to whichCamera
// could be myCamera (if in normal mode)
// or could be viewFrustumOffsetCamera if in offset mode
glm::quat rotation = whichCamera.getRotation();
glm::vec3 axis = glm::axis(rotation);
glRotatef(-glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
// store view matrix without translation, which we'll use for precision-sensitive objects
updateUntranslatedViewMatrix(-whichCamera.getPosition());
glTranslatef(_viewMatrixTranslation.x, _viewMatrixTranslation.y, _viewMatrixTranslation.z);
// Setup 3D lights (after the camera transform, so that they are positioned in world space)
{
PerformanceTimer perfTimer("lights");
setupWorldLight();
}
// setup shadow matrices (again, after the camera transform)
int shadowMatrixCount = 0;
if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) {
shadowMatrixCount = 1;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) {
shadowMatrixCount = CASCADED_SHADOW_MATRIX_COUNT;
}
for (int i = shadowMatrixCount - 1; i >= 0; i--) {
glActiveTexture(GL_TEXTURE0 + i);
glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&_shadowMatrices[i][0]);
glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&_shadowMatrices[i][1]);
glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&_shadowMatrices[i][2]);
}
if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
PerformanceTimer perfTimer("stars");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... stars...");
if (!_stars.isStarsLoaded()) {
_stars.generate(STARFIELD_NUM_STARS, STARFIELD_SEED);
}
// should be the first rendering pass - w/o depth buffer / lighting
// compute starfield alpha based on distance from atmosphere
float alpha = 1.0f;
if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
const EnvironmentData& closestData = _environment.getClosestData(whichCamera.getPosition());
float height = glm::distance(whichCamera.getPosition(),
closestData.getAtmosphereCenter(whichCamera.getPosition()));
if (height < closestData.getAtmosphereInnerRadius()) {
alpha = 0.0f;
} else if (height < closestData.getAtmosphereOuterRadius()) {
alpha = (height - closestData.getAtmosphereInnerRadius()) /
(closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius());
}
}
// finally render the starfield
_stars.render(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), whichCamera.getNearClip(), alpha);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Wireframe)) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
// draw the sky dome
if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
PerformanceTimer perfTimer("atmosphere");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... atmosphere...");
_environment.renderAtmospheres(whichCamera);
}
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
_deferredLightingEffect.prepare();
if (!selfAvatarOnly) {
// draw a red sphere
float originSphereRadius = 0.05f;
glColor3f(1,0,0);
_geometryCache.renderSphere(originSphereRadius, 15, 15);
// draw the audio reflector overlay
{
PerformanceTimer perfTimer("audio");
_audioReflector.render();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) {
PerformanceTimer perfTimer("buckyBalls");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... bucky balls...");
_buckyBalls.render();
}
// Draw voxels
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
PerformanceTimer perfTimer("voxels");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxels...");
_voxels.render();
}
// also, metavoxels
if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) {
PerformanceTimer perfTimer("metavoxels");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... metavoxels...");
_metavoxels.render();
}
// render models...
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
PerformanceTimer perfTimer("entities");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... entities...");
_entities.render();
}
// render JS/scriptable overlays
{
PerformanceTimer perfTimer("3dOverlays");
_overlays.render3D();
}
// render the ambient occlusion effect if enabled
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
PerformanceTimer perfTimer("ambientOcclusion");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... AmbientOcclusion...");
_ambientOcclusionEffect.render();
}
}
bool mirrorMode = (whichCamera.getMode() == CAMERA_MODE_MIRROR);
{
PerformanceTimer perfTimer("avatars");
_avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE,
false, selfAvatarOnly);
}
{
PROFILE_RANGE("DeferredLighting");
PerformanceTimer perfTimer("lighting");
_deferredLightingEffect.render();
}
{
PerformanceTimer perfTimer("avatarsPostLighting");
_avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE,
true, selfAvatarOnly);
}
//Render the sixense lasers
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseLasers)) {
_myAvatar->renderLaserPointers();
}
if (!selfAvatarOnly) {
_nodeBoundsDisplay.draw();
// Render the world box
if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) {
PerformanceTimer perfTimer("worldBox");
renderWorldBox();
}
// view frustum for debugging
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum) && whichCamera.getMode() != CAMERA_MODE_MIRROR) {
PerformanceTimer perfTimer("viewFrustum");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... renderViewFrustum...");
renderViewFrustum(_viewFrustum);
}
// render voxel fades if they exist
if (_voxelFades.size() > 0) {
PerformanceTimer perfTimer("voxelFades");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxel fades...");
_voxelFadesLock.lockForWrite();
for(std::vector<VoxelFade>::iterator fade = _voxelFades.begin(); fade != _voxelFades.end();) {
fade->render();
if(fade->isDone()) {
fade = _voxelFades.erase(fade);
} else {
++fade;
}
}
_voxelFadesLock.unlock();
}
// give external parties a change to hook in
{
PerformanceTimer perfTimer("inWorldInterface");
emit renderingInWorldInterface();
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Wireframe)) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
void Application::updateUntranslatedViewMatrix(const glm::vec3& viewMatrixTranslation) {
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&_untranslatedViewMatrix);
_viewMatrixTranslation = viewMatrixTranslation;
}
void Application::loadTranslatedViewMatrix(const glm::vec3& translation) {
glLoadMatrixf((const GLfloat*)&_untranslatedViewMatrix);
glTranslatef(translation.x + _viewMatrixTranslation.x, translation.y + _viewMatrixTranslation.y,
translation.z + _viewMatrixTranslation.z);
}
void Application::getModelViewMatrix(glm::dmat4* modelViewMatrix) {
(*modelViewMatrix) =_untranslatedViewMatrix;
(*modelViewMatrix)[3] = _untranslatedViewMatrix * glm::vec4(_viewMatrixTranslation, 1);
}
void Application::getProjectionMatrix(glm::dmat4* projectionMatrix) {
*projectionMatrix = _projectionMatrix;
}
void Application::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const {
// allow 3DTV/Oculus to override parameters from camera
_displayViewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
if (OculusManager::isConnected()) {
OculusManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
} else if (TV3DManager::isConnected()) {
TV3DManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
}
}
glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) {
float horizontalScale = _glWidget->getDeviceWidth() / 2.0f;
float verticalScale = _glWidget->getDeviceHeight() / 2.0f;
// -1,-1 is 0,windowHeight
// 1,1 is windowWidth,0
// -1,1 1,1
// +-----------------------+
// | | |
// | | |
// | -1,0 | |
// |-----------+-----------|
// | 0,0 |
// | | |
// | | |
// | | |
// +-----------------------+
// -1,-1 1,-1
glm::vec2 screenPoint((projectedPoint.x + 1.0) * horizontalScale,
((projectedPoint.y + 1.0) * -verticalScale) + _glWidget->getDeviceHeight());
return screenPoint;
}
void Application::renderRearViewMirror(const QRect& region, bool billboard) {
// Grab current viewport to reset it at the end
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
bool eyeRelativeCamera = false;
if (billboard) {
_mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees
_mirrorCamera.setPosition(_myAvatar->getPosition() +
_myAvatar->getOrientation() * glm::vec3(0.f, 0.f, -1.0f) * BILLBOARD_DISTANCE * _myAvatar->getScale());
} else if (_rearMirrorTools->getZoomLevel() == BODY) {
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees
_mirrorCamera.setPosition(_myAvatar->getChestPosition() +
_myAvatar->getOrientation() * glm::vec3(0.f, 0.f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
} else { // HEAD zoom level
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees
if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) {
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
// face/body so that the average eye position lies at the origin
eyeRelativeCamera = true;
_mirrorCamera.setPosition(_myAvatar->getOrientation() * glm::vec3(0.f, 0.f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
} else {
_mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() +
_myAvatar->getOrientation() * glm::vec3(0.f, 0.f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
}
}
_mirrorCamera.setAspectRatio((float)region.width() / region.height());
_mirrorCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f)));
_mirrorCamera.update(1.0f/_fps);
// set the bounds of rear mirror view
if (billboard) {
QSize size = getTextureCache()->getFrameBufferSize();
glViewport(region.x(), size.height() - region.y() - region.height(), region.width(), region.height());
glScissor(region.x(), size.height() - region.y() - region.height(), region.width(), region.height());
} else {
// if not rendering the billboard, the region is in device independent coordinates; must convert to device
QSize size = getTextureCache()->getFrameBufferSize();
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale();
int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio;
glViewport(x, size.height() - y - height, width, height);
glScissor(x, size.height() - y - height, width, height);
}
bool updateViewFrustum = false;
updateProjectionMatrix(_mirrorCamera, updateViewFrustum);
glEnable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// render rear mirror view
glPushMatrix();
if (eyeRelativeCamera) {
// save absolute translations
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
// get the neck position so we can translate the face relative to it
glm::vec3 neckPosition;
_myAvatar->getSkeletonModel().setTranslation(glm::vec3());
_myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
// get the eye position relative to the body
glm::vec3 eyePosition = _myAvatar->getHead()->getEyePosition();
float eyeHeight = eyePosition.y - _myAvatar->getPosition().y;
// set the translation of the face relative to the neck position
_myAvatar->getHead()->getFaceModel().setTranslation(neckPosition - glm::vec3(0, eyeHeight, 0));
// translate the neck relative to the face
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
neckPosition);
// update the attachments to match
QVector<glm::vec3> absoluteAttachmentTranslations;
glm::vec3 delta = _myAvatar->getSkeletonModel().getTranslation() - absoluteSkeletonTranslation;
foreach (Model* attachment, _myAvatar->getAttachmentModels()) {
absoluteAttachmentTranslations.append(attachment->getTranslation());
attachment->setTranslation(attachment->getTranslation() + delta);
}
// and lo, even the shadow matrices
glm::mat4 savedShadowMatrices[CASCADED_SHADOW_MATRIX_COUNT];
for (int i = 0; i < CASCADED_SHADOW_MATRIX_COUNT; i++) {
savedShadowMatrices[i] = _shadowMatrices[i];
_shadowMatrices[i] = glm::transpose(glm::transpose(_shadowMatrices[i]) * glm::translate(-delta));
}
displaySide(_mirrorCamera, true);
// restore absolute translations
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
for (int i = 0; i < absoluteAttachmentTranslations.size(); i++) {
_myAvatar->getAttachmentModels().at(i)->setTranslation(absoluteAttachmentTranslations.at(i));
}
// restore the shadow matrices
for (int i = 0; i < CASCADED_SHADOW_MATRIX_COUNT; i++) {
_shadowMatrices[i] = savedShadowMatrices[i];
}
} else {
displaySide(_mirrorCamera, true);
}
glPopMatrix();
if (!billboard) {
_rearMirrorTools->render(false);
}
// reset Viewport and projection matrix
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
glDisable(GL_SCISSOR_TEST);
updateProjectionMatrix(_myCamera, updateViewFrustum);
}
// renderViewFrustum()
//
// Description: this will render the view frustum bounds for EITHER the head
// or the "myCamera".
//
// Frustum rendering mode. For debug purposes, we allow drawing the frustum in a couple of different ways.
// We can draw it with each of these parts:
// * Origin Direction/Up/Right vectors - these will be drawn at the point of the camera
// * Near plane - this plane is drawn very close to the origin point.
// * Right/Left planes - these two planes are drawn between the near and far planes.
// * Far plane - the plane is drawn in the distance.
// Modes - the following modes, will draw the following parts.
// * All - draws all the parts listed above
// * Planes - draws the planes but not the origin vectors
// * Origin Vectors - draws the origin vectors ONLY
// * Near Plane - draws only the near plane
// * Far Plane - draws only the far plane
void Application::renderViewFrustum(ViewFrustum& viewFrustum) {
// Load it with the latest details!
loadViewFrustum(_myCamera, viewFrustum);
glm::vec3 position = viewFrustum.getOffsetPosition();
glm::vec3 direction = viewFrustum.getOffsetDirection();
glm::vec3 up = viewFrustum.getOffsetUp();
glm::vec3 right = viewFrustum.getOffsetRight();
// Get ready to draw some lines
glDisable(GL_LIGHTING);
glColor4f(1.0, 1.0, 1.0, 1.0);
glLineWidth(1.0);
glBegin(GL_LINES);
if (Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_ALL
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_VECTORS) {
// Calculate the origin direction vectors
glm::vec3 lookingAt = position + (direction * 0.2f);
glm::vec3 lookingAtUp = position + (up * 0.2f);
glm::vec3 lookingAtRight = position + (right * 0.2f);
// Looking At = white
glColor3f(1,1,1);
glVertex3f(position.x, position.y, position.z);
glVertex3f(lookingAt.x, lookingAt.y, lookingAt.z);
// Looking At Up = purple
glColor3f(1,0,1);
glVertex3f(position.x, position.y, position.z);
glVertex3f(lookingAtUp.x, lookingAtUp.y, lookingAtUp.z);
// Looking At Right = cyan
glColor3f(0,1,1);
glVertex3f(position.x, position.y, position.z);
glVertex3f(lookingAtRight.x, lookingAtRight.y, lookingAtRight.z);
}
if (Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_ALL
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_PLANES
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_NEAR_PLANE) {
// Drawing the bounds of the frustum
// viewFrustum.getNear plane - bottom edge
glColor3f(1,0,0);
glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z);
glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z);
// viewFrustum.getNear plane - top edge
glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z);
glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z);
// viewFrustum.getNear plane - right edge
glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z);
glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z);
// viewFrustum.getNear plane - left edge
glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z);
glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z);
}
if (Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_ALL
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_PLANES
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_FAR_PLANE) {
// viewFrustum.getFar plane - bottom edge
glColor3f(0,1,0);
glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z);
glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z);
// viewFrustum.getFar plane - top edge
glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z);
glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z);
// viewFrustum.getFar plane - right edge
glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z);
glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z);
// viewFrustum.getFar plane - left edge
glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z);
glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z);
}
if (Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_ALL
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_PLANES) {
// RIGHT PLANE IS CYAN
// right plane - bottom edge - viewFrustum.getNear to distant
glColor3f(0,1,1);
glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z);
glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z);
// right plane - top edge - viewFrustum.getNear to distant
glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z);
glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z);
// LEFT PLANE IS BLUE
// left plane - bottom edge - viewFrustum.getNear to distant
glColor3f(0,0,1);
glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z);
glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z);
// left plane - top edge - viewFrustum.getNear to distant
glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z);
glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z);
// focal plane - bottom edge
glColor3f(1.0f, 0.0f, 1.0f);
float focalProportion = (viewFrustum.getFocalLength() - viewFrustum.getNearClip()) /
(viewFrustum.getFarClip() - viewFrustum.getNearClip());
glm::vec3 focalBottomLeft = glm::mix(viewFrustum.getNearBottomLeft(), viewFrustum.getFarBottomLeft(), focalProportion);
glm::vec3 focalBottomRight = glm::mix(viewFrustum.getNearBottomRight(),
viewFrustum.getFarBottomRight(), focalProportion);
glVertex3f(focalBottomLeft.x, focalBottomLeft.y, focalBottomLeft.z);
glVertex3f(focalBottomRight.x, focalBottomRight.y, focalBottomRight.z);
// focal plane - top edge
glm::vec3 focalTopLeft = glm::mix(viewFrustum.getNearTopLeft(), viewFrustum.getFarTopLeft(), focalProportion);
glm::vec3 focalTopRight = glm::mix(viewFrustum.getNearTopRight(), viewFrustum.getFarTopRight(), focalProportion);
glVertex3f(focalTopLeft.x, focalTopLeft.y, focalTopLeft.z);
glVertex3f(focalTopRight.x, focalTopRight.y, focalTopRight.z);
// focal plane - left edge
glVertex3f(focalBottomLeft.x, focalBottomLeft.y, focalBottomLeft.z);
glVertex3f(focalTopLeft.x, focalTopLeft.y, focalTopLeft.z);
// focal plane - right edge
glVertex3f(focalBottomRight.x, focalBottomRight.y, focalBottomRight.z);
glVertex3f(focalTopRight.x, focalTopRight.y, focalTopRight.z);
}
glEnd();
glEnable(GL_LIGHTING);
if (Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_ALL
|| Menu::getInstance()->getFrustumDrawMode() == FRUSTUM_DRAW_MODE_KEYHOLE) {
// Draw the keyhole
float keyholeRadius = viewFrustum.getKeyholeRadius();
if (keyholeRadius > 0.0f) {
glPushMatrix();
glColor4f(1, 1, 0, 1);
glTranslatef(position.x, position.y, position.z); // where we actually want it!
glutWireSphere(keyholeRadius, 20, 20);
glPopMatrix();
}
}
}
void Application::deleteVoxels(const VoxelDetail& voxel) {
deleteVoxelAt(voxel);
}
void Application::deleteVoxelAt(const VoxelDetail& voxel) {
if (voxel.s != 0) {
// sending delete to the server is sufficient, server will send new version so we see updates soon enough
_voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, voxel);
// delete it locally to see the effect immediately (and in case no voxel server is present)
_voxels.getTree()->deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s);
}
}
void Application::resetSensors() {
_mouseX = _glWidget->width() / 2;
_mouseY = _glWidget->height() / 2;
_faceshift.reset();
_visage.reset();
_dde.reset();
OculusManager::reset();
_prioVR.reset();
//_leapmotion.reset();
QCursor::setPos(_mouseX, _mouseY);
_myAvatar->reset();
QMetaObject::invokeMethod(&_audio, "reset", Qt::QueuedConnection);
}
static void setShortcutsEnabled(QWidget* widget, bool enabled) {
foreach (QAction* action, widget->actions()) {
QKeySequence shortcut = action->shortcut();
if (!shortcut.isEmpty() && (shortcut[0] & (Qt::CTRL | Qt::ALT | Qt::META)) == 0) {
// it's a shortcut that may coincide with a "regular" key, so switch its context
action->setShortcutContext(enabled ? Qt::WindowShortcut : Qt::WidgetShortcut);
}
}
foreach (QObject* child, widget->children()) {
if (child->isWidgetType()) {
setShortcutsEnabled(static_cast<QWidget*>(child), enabled);
}
}
}
void Application::setMenuShortcutsEnabled(bool enabled) {
setShortcutsEnabled(_window->menuBar(), enabled);
}
void Application::uploadModel(ModelType modelType) {
ModelUploader* uploader = new ModelUploader(modelType);
QThread* thread = new QThread();
thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit()));
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
uploader->connect(thread, SIGNAL(started()), SLOT(send()));
thread->start();
}
void Application::updateWindowTitle(){
QString buildVersion = " (build " + applicationVersion() + ")";
NodeList* nodeList = NodeList::getInstance();
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) ";
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
+ AddressManager::getInstance().getCurrentDomain() + connectionStatus + buildVersion;
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.getAccountInfo().hasBalance()) {
float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT;
QString creditBalanceString;
creditBalanceString.sprintf("%.8f", creditBalance);
title += " - ₵" + creditBalanceString;
}
#ifndef WIN32
// crashes with vs2013/win32
qDebug("Application title set to: %s", title.toStdString().c_str());
#endif
_window->setWindowTitle(title);
}
void Application::updateLocationInServer() {
AccountManager& accountManager = AccountManager::getInstance();
DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler();
if (accountManager.isLoggedIn() && domainHandler.isConnected() && !domainHandler.getUUID().isNull()) {
// construct a QJsonObject given the user's current address information
QJsonObject rootObject;
QJsonObject locationObject;
QString pathString = AddressManager::getInstance().currentPath();
const QString LOCATION_KEY_IN_ROOT = "location";
const QString PATH_KEY_IN_LOCATION = "path";
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainHandler.getUUID().toString());
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
}
}
void Application::changeDomainHostname(const QString &newDomainHostname) {
NodeList* nodeList = NodeList::getInstance();
if (!nodeList->getDomainHandler().isCurrentHostname(newDomainHostname)) {
// tell the MyAvatar object to send a kill packet so that it dissapears from its old avatar mixer immediately
_myAvatar->sendKillAvatar();
// call the domain hostname change as a queued connection on the nodelist
QMetaObject::invokeMethod(&NodeList::getInstance()->getDomainHandler(), "setHostname",
Q_ARG(const QString&, newDomainHostname));
}
}
void Application::clearDomainOctreeDetails() {
qDebug() << "Clearing domain octree details...";
// reset the environment so that we don't erroneously end up with multiple
_environment.resetToDefault();
// reset our node to stats and node to jurisdiction maps... since these must be changing...
_voxelServerJurisdictions.lockForWrite();
_voxelServerJurisdictions.clear();
_voxelServerJurisdictions.unlock();
_entityServerJurisdictions.lockForWrite();
_entityServerJurisdictions.clear();
_entityServerJurisdictions.unlock();
_octreeSceneStatsLock.lockForWrite();
_octreeServerSceneStats.clear();
_octreeSceneStatsLock.unlock();
// reset the model renderer
_entities.clear();
// reset the voxels renderer
_voxels.killLocalVoxels();
}
void Application::domainChanged(const QString& domainHostname) {
updateWindowTitle();
clearDomainOctreeDetails();
}
void Application::connectedToDomain(const QString& hostname) {
AccountManager& accountManager = AccountManager::getInstance();
const QUuid& domainID = NodeList::getInstance()->getDomainHandler().getUUID();
if (accountManager.isLoggedIn() && !domainID.isNull()) {
// update our data-server with the domain-server we're logged in with
QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), domainPutJsonString.toUtf8());
}
}
void Application::nodeAdded(SharedNodePointer node) {
if (node->getType() == NodeType::AvatarMixer) {
// new avatar mixer, send off our identity packet right away
_myAvatar->sendIdentityPacket();
}
}
void Application::nodeKilled(SharedNodePointer node) {
// These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work:
// OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted.
// This may have to do with GenericThread::threadRoutine() blocking the QThread event loop
_octreeProcessor.nodeKilled(node);
_voxelEditSender.nodeKilled(node);
_entityEditSender.nodeKilled(node);
if (node->getType() == NodeType::AudioMixer) {
QMetaObject::invokeMethod(&_audio, "audioMixerKilled");
}
if (node->getType() == NodeType::VoxelServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
_voxelServerJurisdictions.lockForRead();
if (_voxelServerJurisdictions.find(nodeUUID) != _voxelServerJurisdictions.end()) {
unsigned char* rootCode = _voxelServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
_voxelServerJurisdictions.unlock();
qDebug("voxel server going away...... v[%f, %f, %f, %f]",
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
// Add the jurisditionDetails object to the list of "fade outs"
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE);
fade.voxelDetails = rootDetails;
const float slightly_smaller = 0.99f;
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
_voxelFadesLock.lockForWrite();
_voxelFades.push_back(fade);
_voxelFadesLock.unlock();
}
// If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
_voxelServerJurisdictions.lockForWrite();
_voxelServerJurisdictions.erase(_voxelServerJurisdictions.find(nodeUUID));
}
_voxelServerJurisdictions.unlock();
// also clean up scene stats for that server
_octreeSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
_octreeServerSceneStats.erase(nodeUUID);
}
_octreeSceneStatsLock.unlock();
} else if (node->getType() == NodeType::EntityServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
_entityServerJurisdictions.lockForRead();
if (_entityServerJurisdictions.find(nodeUUID) != _entityServerJurisdictions.end()) {
unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
_entityServerJurisdictions.unlock();
qDebug("model server going away...... v[%f, %f, %f, %f]",
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
// Add the jurisditionDetails object to the list of "fade outs"
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE);
fade.voxelDetails = rootDetails;
const float slightly_smaller = 0.99f;
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
_voxelFadesLock.lockForWrite();
_voxelFades.push_back(fade);
_voxelFadesLock.unlock();
}
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
_entityServerJurisdictions.lockForWrite();
_entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID));
}
_entityServerJurisdictions.unlock();
// also clean up scene stats for that server
_octreeSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
_octreeServerSceneStats.erase(nodeUUID);
}
_octreeSceneStatsLock.unlock();
} else if (node->getType() == NodeType::AvatarMixer) {
// our avatar mixer has gone away - clear the hash of avatars
_avatarManager.clearOtherAvatars();
}
}
void Application::trackIncomingVoxelPacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) {
// Attempt to identify the sender from it's address.
if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node...
_octreeSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec());
}
_octreeSceneStatsLock.unlock();
}
}
int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePointer& sendingNode) {
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
// parse the incoming stats datas stick it in a temporary object for now, while we
// determine which server it belongs to
OctreeSceneStats temp;
int statsMessageLength = temp.unpackFromMessage(reinterpret_cast<const unsigned char*>(packet.data()), packet.size());
// quick fix for crash... why would voxelServer be NULL?
if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node...
_octreeSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
_octreeServerSceneStats[nodeUUID].unpackFromMessage(reinterpret_cast<const unsigned char*>(packet.data()),
packet.size());
} else {
_octreeServerSceneStats[nodeUUID] = temp;
}
_octreeSceneStatsLock.unlock();
VoxelPositionSize rootDetails;
voxelDetailsForCode(temp.getJurisdictionRoot(), rootDetails);
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
QString serverType;
if (sendingNode->getType() == NodeType::VoxelServer) {
jurisdiction = &_voxelServerJurisdictions;
serverType = "Voxel";
} else {
jurisdiction = &_entityServerJurisdictions;
serverType = "Entity";
}
jurisdiction->lockForRead();
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) {
jurisdiction->unlock();
qDebug("stats from new %s server... [%f, %f, %f, %f]",
qPrintable(serverType), rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
// Add the jurisditionDetails object to the list of "fade outs"
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
VoxelFade fade(VoxelFade::FADE_OUT, NODE_ADDED_RED, NODE_ADDED_GREEN, NODE_ADDED_BLUE);
fade.voxelDetails = rootDetails;
const float slightly_smaller = 0.99f;
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
_voxelFadesLock.lockForWrite();
_voxelFades.push_back(fade);
_voxelFadesLock.unlock();
}
} else {
jurisdiction->unlock();
}
// store jurisdiction details for later use
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
// but OctreeSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
jurisdiction->lockForWrite();
(*jurisdiction)[nodeUUID] = jurisdictionMap;
jurisdiction->unlock();
}
return statsMessageLength;
}
void Application::packetSent(quint64 length) {
_bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length);
}
void Application::loadScripts() {
// loads all saved scripts
int size = lockSettings()->beginReadArray("Settings");
unlockSettings();
for (int i = 0; i < size; ++i){
lockSettings()->setArrayIndex(i);
QString string = _settings->value("script").toString();
unlockSettings();
if (!string.isEmpty()) {
loadScript(string);
}
}
QMutexLocker locker(&_settingsMutex);
_settings->endArray();
}
void Application::clearScriptsBeforeRunning() {
// clears all scripts from the settings
QMutexLocker locker(&_settingsMutex);
_settings->remove("Settings");
}
void Application::saveScripts() {
// Saves all currently running user-loaded scripts
QMutexLocker locker(&_settingsMutex);
_settings->beginWriteArray("Settings");
QStringList runningScripts = getRunningScripts();
int i = 0;
for (QStringList::const_iterator it = runningScripts.begin(); it != runningScripts.end(); it += 1) {
if (getScriptEngine(*it)->isUserLoaded()) {
_settings->setArrayIndex(i);
_settings->setValue("script", *it);
i += 1;
}
}
_settings->endArray();
}
QScriptValue joystickToScriptValue(QScriptEngine *engine, Joystick* const &in) {
return engine->newQObject(in);
}
void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) {
out = qobject_cast<Joystick*>(object.toQObject());
}
void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) {
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
// we can use the same ones from the application.
scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
scriptEngine->getVoxelsScriptingInterface()->setVoxelTree(_voxels.getTree());
scriptEngine->getVoxelsScriptingInterface()->setUndoStack(&_undoStack);
scriptEngine->getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
scriptEngine->getEntityScriptingInterface()->setEntityTree(_entities.getTree());
// AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine);
// hook our avatar and avatar hash map object into this script engine
scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
scriptEngine->setAvatarHashMap(&_avatarManager, "AvatarList");
scriptEngine->registerGlobalObject("Camera", &_myCamera);
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
scriptEngine->registerGlobalObject("SpeechRecognizer", Menu::getInstance()->getSpeechRecognizer());
#endif
ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface();
scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater()));
connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&)));
connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool)));
scriptEngine->registerGlobalObject("Overlays", &_overlays);
qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue);
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter, windowValue);
// register `location` on the global object.
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AnimationCache", &_animationCache);
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Metavoxels", &_metavoxels);
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AvatarManager", &_avatarManager);
scriptEngine->registerGlobalObject("Joysticks", &JoystickScriptingInterface::getInstance());
qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue);
scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface);
#ifdef HAVE_RTMIDI
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
#endif
QThread* workerThread = new QThread(this);
// when the worker thread is started, call our engine's run..
connect(workerThread, &QThread::started, scriptEngine, &ScriptEngine::run);
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
// when the application is about to quit, stop our script engine so it unwinds properly
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
NodeList* nodeList = NodeList::getInstance();
connect(nodeList, &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
scriptEngine->moveToThread(workerThread);
// Starts an event loop, and emits workerThread->started()
workerThread->start();
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
registerScriptEngineWithApplicationServices(scriptEngine);
// restore the main window's active state
if (activateMainWindow && !loadScriptFromEditor) {
_window->activateWindow();
}
bumpSettings();
return scriptEngine;
}
void Application::scriptFinished(const QString& scriptName) {
const QString& scriptURLString = QUrl(scriptName).toString();
QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptURLString);
if (it != _scriptEnginesHash.end()) {
_scriptEnginesHash.erase(it);
_runningScriptsWidget->scriptStopped(scriptName);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
bumpSettings();
}
}
void Application::stopAllScripts(bool restart) {
// stops all current running scripts
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
it != _scriptEnginesHash.constEnd(); it++) {
if (restart && it.value()->isUserLoaded()) {
connect(it.value(), SIGNAL(finished(const QString&)), SLOT(loadScript(const QString&)));
}
it.value()->stop();
qDebug() << "stopping script..." << it.key();
}
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
// whenever a script stops in case it happened to have been setting joint rotations.
// TODO: expose animation priorities and provide a layered animation control system.
_myAvatar->clearScriptableSettings();
}
void Application::stopScript(const QString &scriptName) {
const QString& scriptURLString = QUrl(scriptName).toString();
if (_scriptEnginesHash.contains(scriptURLString)) {
_scriptEnginesHash.value(scriptURLString)->stop();
qDebug() << "stopping script..." << scriptName;
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
// whenever a script stops in case it happened to have been setting joint rotations.
// TODO: expose animation priorities and provide a layered animation control system.
_myAvatar->clearJointAnimationPriorities();
}
if (_scriptEnginesHash.empty()) {
_myAvatar->clearScriptableSettings();
}
}
void Application::reloadAllScripts() {
stopAllScripts(true);
}
void Application::loadDefaultScripts() {
if (!_scriptEnginesHash.contains(DEFAULT_SCRIPTS_JS_URL)) {
loadScript(DEFAULT_SCRIPTS_JS_URL);
}
}
void Application::manageRunningScriptsWidgetVisibility(bool shown) {
if (_runningScriptsWidgetWasVisible && shown) {
_runningScriptsWidget->show();
} else if (_runningScriptsWidgetWasVisible && !shown) {
_runningScriptsWidget->hide();
}
}
void Application::toggleRunningScriptsWidget() {
if (_runningScriptsWidget->isVisible()) {
if (_runningScriptsWidget->hasFocus()) {
_runningScriptsWidget->hide();
} else {
_runningScriptsWidget->raise();
setActiveWindow(_runningScriptsWidget);
_runningScriptsWidget->setFocus();
}
} else {
_runningScriptsWidget->show();
_runningScriptsWidget->setFocus();
}
}
void Application::uploadHead() {
uploadModel(HEAD_MODEL);
}
void Application::uploadSkeleton() {
uploadModel(SKELETON_MODEL);
}
void Application::uploadAttachment() {
uploadModel(ATTACHMENT_MODEL);
}
void Application::openUrl(const QUrl& url) {
if (!url.isEmpty()) {
if (url.scheme() == HIFI_URL_SCHEME) {
AddressManager::getInstance().handleLookupString(url.toString());
} else {
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
}
}
}
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
const QString VOXEL_SETTINGS_KEY = "voxels";
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
const QString VOXEL_WALLET_UUID = "voxel-wallet";
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
qint64 satoshisPerVoxel = 0;
qint64 satoshisPerMeterCubed = 0;
QUuid voxelWalletUUID;
if (!domainSettingsObject.isEmpty()) {
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
}
qDebug() << "Voxel costs are" << satoshisPerVoxel << "per voxel and" << satoshisPerMeterCubed << "per meter cubed";
qDebug() << "Destination wallet UUID for voxel payments is" << voxelWalletUUID;
_voxelEditSender.setSatoshisPerVoxel(satoshisPerVoxel);
_voxelEditSender.setSatoshisPerMeterCubed(satoshisPerMeterCubed);
_voxelEditSender.setDestinationWalletUUID(voxelWalletUUID);
}
QString Application::getPreviousScriptLocation() {
QString suggestedName;
if (_previousScriptLocation.isEmpty()) {
QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475
#ifdef __APPLE__
suggestedName = desktopLocation.append("/script.js");
#endif
} else {
suggestedName = _previousScriptLocation;
}
return suggestedName;
}
void Application::setPreviousScriptLocation(const QString& previousScriptLocation) {
_previousScriptLocation = previousScriptLocation;
QMutexLocker locker(&_settingsMutex);
_settings->setValue("LastScriptLocation", _previousScriptLocation);
bumpSettings();
}
void Application::loadDialog() {
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"),
getPreviousScriptLocation(),
tr("JavaScript Files (*.js)"));
if (!fileNameString.isEmpty()) {
setPreviousScriptLocation(fileNameString);
loadScript(fileNameString);
}
}
void Application::loadScriptURLDialog() {
QInputDialog scriptURLDialog(Application::getInstance()->getWindow());
scriptURLDialog.setWindowTitle("Open and Run Script URL");
scriptURLDialog.setLabelText("Script:");
scriptURLDialog.setWindowFlags(Qt::Sheet);
const float DIALOG_RATIO_OF_WINDOW = 0.30f;
scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
scriptURLDialog.size().height());
int dialogReturn = scriptURLDialog.exec();
QString newScript;
if (dialogReturn == QDialog::Accepted) {
if (scriptURLDialog.textValue().size() > 0) {
// the user input a new hostname, use that
newScript = scriptURLDialog.textValue();
}
loadScript(newScript);
}
sendFakeEnterEvent();
}
void Application::toggleLogDialog() {
if (! _logDialog) {
_logDialog = new LogDialog(_glWidget, getLogger());
}
if (_logDialog->isVisible()) {
_logDialog->hide();
} else {
_logDialog->show();
}
}
void Application::initAvatarAndViewFrustum() {
updateMyAvatar(0.f);
}
void Application::checkVersion() {
QNetworkRequest latestVersionRequest((QUrl(CHECK_VERSION_URL)));
latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
QNetworkReply* reply = NetworkAccessManager::getInstance().get(latestVersionRequest);
connect(reply, SIGNAL(finished()), SLOT(parseVersionXml()));
}
void Application::parseVersionXml() {
#ifdef Q_OS_WIN32
QString operatingSystem("win");
#endif
#ifdef Q_OS_MAC
QString operatingSystem("mac");
#endif
#ifdef Q_OS_LINUX
QString operatingSystem("ubuntu");
#endif
QString latestVersion;
QUrl downloadUrl;
QString releaseNotes("Unavailable");
QObject* sender = QObject::sender();
QXmlStreamReader xml(qobject_cast<QNetworkReply*>(sender));
while (!xml.atEnd() && !xml.hasError()) {
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == operatingSystem) {
while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == operatingSystem)) {
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "version") {
xml.readNext();
latestVersion = xml.text().toString();
}
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "url") {
xml.readNext();
downloadUrl = QUrl(xml.text().toString());
}
xml.readNext();
}
}
xml.readNext();
}
if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) {
new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl);
}
sender->deleteLater();
}
bool Application::shouldSkipVersion(QString latestVersion) {
QFile skipFile(SKIP_FILENAME);
skipFile.open(QIODevice::ReadWrite);
QString skipVersion(skipFile.readAll());
return (skipVersion == latestVersion || applicationVersion() == "dev");
}
void Application::skipVersion(QString latestVersion) {
QFile skipFile(SKIP_FILENAME);
skipFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
skipFile.seek(0);
skipFile.write(latestVersion.toStdString().c_str());
}
void Application::setCursorVisible(bool visible) {
if (visible) {
restoreOverrideCursor();
} else {
setOverrideCursor(Qt::BlankCursor);
}
}
void Application::takeSnapshot() {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(Application::resourcesPath() + "sounds/snap.wav");
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
QString fileName = Snapshot::saveSnapshot();
AccountManager& accountManager = AccountManager::getInstance();
if (!accountManager.isLoggedIn()) {
return;
}
if (!_snapshotShareDialog) {
_snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget);
}
_snapshotShareDialog->show();
}
void Application::setVSyncEnabled(bool vsyncOn) {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
wglSwapIntervalEXT(vsyncOn);
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
glxSwapIntervalEXT(vsyncOn);
int swapInterval = xglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
*/
#else
qDebug("V-Sync is FORCED ON on this system\n");
#endif
}
bool Application::isVSyncOn() const {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
return (swapInterval > 0);
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
int swapInterval = xglGetSwapIntervalEXT();
return (swapInterval > 0);
} else {
return true;
}
*/
#endif
return true;
}
bool Application::isVSyncEditable() const {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
return true;
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
return true;
}
*/
#endif
return false;
}
unsigned int Application::getRenderTargetFramerate() const {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) {
return 0;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) {
return 60;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) {
return 50;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) {
return 40;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) {
return 30;
}
return 0;
}
float Application::getRenderResolutionScale() const {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) {
return 1.f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) {
return 0.666f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) {
return 0.5f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) {
return 0.333f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) {
return 0.25f;
} else {
return 1.f;
}
}