// // Application.cpp // interface // // Created by Andrzej Kapolka on 5/10/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include #include #include #ifdef _WIN32 #include "Syssocket.h" #include "Systime.h" #else #include #include #include #endif #include #include // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Application.h" #include "DataServerClient.h" #include "LogDisplay.h" #include "Menu.h" #include "Swatch.h" #include "Util.h" #include "devices/LeapManager.h" #include "devices/OculusManager.h" #include "renderer/ProgramObject.h" #include "ui/TextRenderer.h" #include "InfoView.h" using namespace std; // Starfield information static char STAR_FILE[] = "http://s3-us-west-1.amazonaws.com/highfidelity/stars.txt"; static char STAR_CACHE_FILE[] = "cachedStars.txt"; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff // in the idle loop? (60 FPS is default) static QTimer* idleTimer = NULL; const int STARTUP_JITTER_SAMPLES = PACKET_LENGTH_SAMPLES_PER_CHANNEL / 2; // Startup optimistically with small jitter buffer that // will start playback on the second received audio packet. void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) { fprintf(stdout, "%s", message.toLocal8Bit().constData()); LogDisplay::instance.addMessage(message.toLocal8Bit().constData()); } Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), _displayLevels(false), _frameCount(0), _fps(120.0f), _justStarted(true), _voxelImporter(_window), _wantToKillLocalVoxels(false), _audioScope(256, 200, true), _mouseX(0), _mouseY(0), _touchAvgX(0.0f), _touchAvgY(0.0f), _isTouchPressed(false), _yawFromTouch(0.0f), _pitchFromTouch(0.0f), _mousePressed(false), _isHoverVoxel(false), _isHoverVoxelSounding(false), _mouseVoxelScale(1.0f / 1024.0f), _justEditedVoxel(false), _nudgeStarted(false), _lookingAlongX(false), _lookingAwayFromOrigin(true), _lookatTargetAvatar(NULL), _lookatIndicatorScale(1.0f), _perfStatsOn(false), _chatEntryOn(false), _oculusProgram(0), _oculusDistortionScale(1.25), #ifndef _WIN32 _audio(&_audioScope, STARTUP_JITTER_SAMPLES), #endif _stopNetworkReceiveThread(false), _voxelProcessor(), _voxelEditSender(this), _packetCount(0), _packetsPerSecond(0), _bytesPerSecond(0), _bytesCount(0), _swatch(NULL), _pasteMode(false) { _applicationStartupTime = startup_time; _window->setWindowTitle("Interface"); qInstallMessageHandler(messageHandler); unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); if (portStr) { listenPort = atoi(portStr); } NodeList::createInstance(NODE_TYPE_AGENT, listenPort); NodeList::getInstance()->addHook(&_voxels); NodeList::getInstance()->addHook(this); NodeList::getInstance()->addDomainListener(this); // network receive thread and voxel parsing thread are both controlled by the --nonblocking command line _enableProcessVoxelsThread = _enableNetworkThread = !cmdOptionExists(argc, constArgv, "--nonblocking"); if (!_enableNetworkThread) { NodeList::getInstance()->getNodeSocket()->setBlocking(false); } // setup QSettings #ifdef Q_OS_MAC QString resourcesPath = QCoreApplication::applicationDirPath() + "/../Resources"; #else QString resourcesPath = QCoreApplication::applicationDirPath() + "/resources"; #endif // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(resourcesPath + "/info/ApplicationInfo.ini", QSettings::IniFormat); // set the associated application properties applicationInfo.beginGroup("INFO"); setApplicationName(applicationInfo.value("name").toString()); setApplicationVersion(applicationInfo.value("version").toString()); setOrganizationName(applicationInfo.value("organizationName").toString()); setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); _settings = new QSettings(this); // check if there is a saved domain server hostname // this must be done now instead of with the other setting checks to allow manual override with // --domain or --local options NodeList::getInstance()->loadData(_settings); // Check to see if the user passed in a command line option for loading a local // Voxel File. _voxelsFilename = getCmdOption(argc, constArgv, "-i"); // the callback for our instance of NodeList is attachNewHeadToNode NodeList::getInstance()->linkedDataCreateCallback = &attachNewHeadToNode; #ifdef _WIN32 WSADATA WsaData; int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData); #endif // tell the NodeList instance who to tell the domain server we care about const char nodeTypesOfInterest[] = {NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER}; NodeList::getInstance()->setNodeTypesOfInterest(nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); // start the nodeList threads NodeList::getInstance()->startSilentNodeRemovalThread(); _window->setCentralWidget(_glWidget); // call Menu getInstance static method to set up the menu _window->setMenuBar(Menu::getInstance()); _networkAccessManager = new QNetworkAccessManager(this); QNetworkDiskCache* cache = new QNetworkDiskCache(_networkAccessManager); cache->setCacheDirectory("interfaceCache"); _networkAccessManager->setCache(cache); QRect available = desktop()->availableGeometry(); _window->resize(available.size()); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); // initialization continues in initializeGL when OpenGL context is ready // Tell our voxel edit sender about our known jurisdictions _voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions); } Application::~Application() { NodeList::getInstance()->removeHook(&_voxels); NodeList::getInstance()->removeHook(this); NodeList::getInstance()->removeDomainListener(this); _sharedVoxelSystem.changeTree(new VoxelTree); _audio.shutdown(); delete Menu::getInstance(); delete _oculusProgram; delete _settings; delete _networkAccessManager; delete _followMode; delete _glWidget; } void Application::initializeGL() { qDebug( "Created Display Window.\n" ); // initialize glut for shape drawing; Qt apparently initializes it on OS X #ifndef __APPLE__ int argc = 0; glutInit(&argc, 0); #endif // Before we render anything, let's set up our viewFrustumOffsetCamera with a sufficiently large // field of view and near and far clip to make it interesting. //viewFrustumOffsetCamera.setFieldOfView(90.0); _viewFrustumOffsetCamera.setNearClip(0.1); _viewFrustumOffsetCamera.setFarClip(500.0 * TREE_SCALE); initDisplay(); qDebug( "Initialized Display.\n" ); init(); qDebug( "Init() complete.\n" ); // Check to see if the user passed in a command line option for randomizing colors bool wantColorRandomizer = !arguments().contains("--NoColorRandomizer"); // Check to see if the user passed in a command line option for loading a local // Voxel File. If so, load it now. if (!_voxelsFilename.isEmpty()) { _voxels.loadVoxelsFile(_voxelsFilename.constData(), wantColorRandomizer); qDebug("Local Voxel File loaded.\n"); } // create thread for receipt of data via UDP if (_enableNetworkThread) { pthread_create(&_networkReceiveThread, NULL, networkReceive, NULL); qDebug("Network receive thread created.\n"); } // create thread for parsing of voxel data independent of the main network and rendering threads _voxelProcessor.initialize(_enableProcessVoxelsThread); _voxelEditSender.initialize(_enableProcessVoxelsThread); if (_enableProcessVoxelsThread) { qDebug("Voxel parsing thread created.\n"); } // call terminate before exiting connect(this, SIGNAL(aboutToQuit()), SLOT(terminate())); // call our timer function every second QTimer* timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timer())); timer->start(1000); // call our idle function whenever we can idleTimer = new QTimer(this); connect(idleTimer, SIGNAL(timeout()), SLOT(idle())); idleTimer->start(0); _idleLoopStdev.reset(); if (_justStarted) { float startupTime = (usecTimestampNow() - usecTimestamp(&_applicationStartupTime)) / 1000000.0; _justStarted = false; char title[50]; sprintf(title, "Interface: %4.2f seconds\n", startupTime); qDebug("%s", title); const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time"; // ask the Logstash class to record the startup time Logging::stashValue(STAT_TYPE_TIMER, LOGSTASH_INTERFACE_START_TIME_KEY, startupTime); } // update before the first render update(0.0f); InfoView::showFirstTime(); } void Application::paintGL() { PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::paintGL()"); PerfStat("display"); glEnable(GL_LINE_SMOOTH); if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness (100.0f); glm::vec3 targetPosition = _myAvatar.getUprightHeadPosition(); if (_myAvatar.getHead().getBlendFace().isActive()) { // make sure we're aligned to the blend face eyes glm::vec3 leftEyePosition, rightEyePosition; if (_myAvatar.getHead().getBlendFace().getEyePositions(leftEyePosition, rightEyePosition, true)) { targetPosition = (leftEyePosition + rightEyePosition) * 0.5f; } } _myCamera.setTargetPosition(targetPosition); _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); } else if (OculusManager::isConnected()) { _myCamera.setUpShift (0.0f); _myCamera.setDistance (0.0f); _myCamera.setTightness (0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar.getHeadJointPosition()); _myCamera.setTargetRotation(_myAvatar.getHead().getOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { _myCamera.setTightness(0.0f); // In first person, camera follows head exactly without delay _myCamera.setTargetPosition(_myAvatar.getUprightEyeLevelPosition()); _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { _myCamera.setTargetPosition(_myAvatar.getUprightHeadPosition()); _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation()); } // Update camera position _myCamera.update( 1.f/_fps ); // Note: whichCamera is used to pick between the normal camera myCamera for our // main camera, vs, an alternate camera. The alternate camera we support right now // is the viewFrustumOffsetCamera. But theoretically, we could use this same mechanism // to add other cameras. // // Why have two cameras? Well, one reason is that because in the case of the renderViewFrustum() // code, we want to keep the state of "myCamera" intact, so we can render what the view frustum of // myCamera is. But we also want to do meaningful camera transforms on OpenGL for the offset camera Camera whichCamera = _myCamera; if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) { ViewFrustumOffset viewFrustumOffset = Menu::getInstance()->getViewFrustumOffset(); // set the camera to third-person view but offset so we can see the frustum _viewFrustumOffsetCamera.setTargetPosition(_myCamera.getTargetPosition()); _viewFrustumOffsetCamera.setTargetRotation(_myCamera.getTargetRotation() * glm::quat(glm::radians(glm::vec3( viewFrustumOffset.pitch, viewFrustumOffset.yaw, viewFrustumOffset.roll)))); _viewFrustumOffsetCamera.setUpShift(viewFrustumOffset.up); _viewFrustumOffsetCamera.setDistance(viewFrustumOffset.distance); _viewFrustumOffsetCamera.initialize(); // force immediate snap to ideal position and orientation _viewFrustumOffsetCamera.update(1.f/_fps); whichCamera = _viewFrustumOffsetCamera; } if (OculusManager::isConnected()) { displayOculus(whichCamera); } else { _glowEffect.prepare(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); displaySide(whichCamera); glPopMatrix(); _glowEffect.render(); displayOverlay(); } _frameCount++; } void Application::resetCamerasOnResizeGL(Camera& camera, int width, int height) { float aspectRatio = ((float)width/(float)height); // based on screen resize if (OculusManager::isConnected()) { // more magic numbers; see Oculus SDK docs, p. 32 camera.setAspectRatio(aspectRatio *= 0.5); camera.setFieldOfView(2 * atan((0.0468 * _oculusDistortionScale) / 0.041) * (180 / PIf)); } else { camera.setAspectRatio(aspectRatio); camera.setFieldOfView(Menu::getInstance()->getFieldOfView()); } } void Application::resizeGL(int width, int height) { resetCamerasOnResizeGL(_viewFrustumOffsetCamera, width, height); resetCamerasOnResizeGL(_myCamera, width, height); glViewport(0, 0, width, height); // shouldn't this account for the menu??? updateProjectionMatrix(); glLoadIdentity(); } void Application::updateProjectionMatrix() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Tell our viewFrustum about this change, using the application camera loadViewFrustum(_myCamera, _viewFrustum); float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); // If we're in Display Frustum mode, then we want to use the slightly adjust near/far clip values of the // _viewFrustumOffsetCamera, so that we can see more of the application content in the application's frustum if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) { nearVal = _viewFrustumOffsetCamera.getNearClip(); farVal = _viewFrustumOffsetCamera.getFarClip(); } glFrustum(left, right, bottom, top, nearVal, farVal); glMatrixMode(GL_MODELVIEW); } void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes, const char* nodeTypes, int numNodeTypes) { Application* self = getInstance(); for (int i = 0; i < numNodeTypes; ++i) { // Intercept data to voxel server when voxels are disabled if (nodeTypes[i] == NODE_TYPE_VOXEL_SERVER && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { continue; } // Perform the broadcast for one type int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(broadcastData, dataBytes, & nodeTypes[i], 1); // Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise) BandwidthMeter::ChannelIndex channel; switch (nodeTypes[i]) { case NODE_TYPE_AGENT: case NODE_TYPE_AVATAR_MIXER: channel = BandwidthMeter::AVATARS; break; case NODE_TYPE_VOXEL_SERVER: channel = BandwidthMeter::VOXELS; break; default: continue; } self->_bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * dataBytes); } } void Application::keyPressEvent(QKeyEvent* event) { if (activeWindow() == _window) { if (_chatEntryOn) { if (_chatEntry.keyPressEvent(event)) { _myAvatar.setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ? DELETE_KEY_DOWN : INSERT_KEY_DOWN); _myAvatar.setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR)); } else { _myAvatar.setChatMessage(_chatEntry.getContents()); _chatEntry.clear(); _chatEntryOn = false; setMenuShortcutsEnabled(true); } return; } //this is for switching between modes for the leap rave glove test if (Menu::getInstance()->isOptionChecked(MenuOption::SimulateLeapHand) || Menu::getInstance()->isOptionChecked(MenuOption::TestRaveGlove)) { _myAvatar.getHand().setRaveGloveEffectsMode((QKeyEvent*)event); } bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); switch (event->key()) { case Qt::Key_Shift: if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) { _pasteMode = true; } 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_Semicolon: _audio.ping(); break; case Qt::Key_Apostrophe: _audioScope.inputPaused = !_audioScope.inputPaused; break; case Qt::Key_L: _displayLevels = !_displayLevels; break; case Qt::Key_E: if (_nudgeStarted) { _nudgeGuidePosition.y += _mouseVoxel.s; } else { if (!_myAvatar.getDriveKeys(UP)) { _myAvatar.jump(); } _myAvatar.setDriveKeys(UP, 1); } break; case Qt::Key_Asterisk: Menu::getInstance()->triggerOption(MenuOption::Stars); break; case Qt::Key_C: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::OcclusionCulling); } else if (_nudgeStarted) { _nudgeGuidePosition.y -= _mouseVoxel.s; } else { _myAvatar.setDriveKeys(DOWN, 1); } break; case Qt::Key_W: if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x += _mouseVoxel.s; } else { _nudgeGuidePosition.x -= _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z += _mouseVoxel.s; } else { _nudgeGuidePosition.z -= _mouseVoxel.s; } } } else { _myAvatar.setDriveKeys(FWD, 1); } break; case Qt::Key_S: if (isShifted) { _voxels.collectStatsForTreesAndVBOs(); } else if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x -= _mouseVoxel.s; } else { _nudgeGuidePosition.x += _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z -= _mouseVoxel.s; } else { _nudgeGuidePosition.z += _mouseVoxel.s; } } } else { _myAvatar.setDriveKeys(BACK, 1); } break; case Qt::Key_Space: resetSensors(); _audio.reset(); break; case Qt::Key_G: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Gravity); } else { Menu::getInstance()->triggerOption(MenuOption::VoxelGetColorMode); } break; case Qt::Key_A: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Atmosphere); } else if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z -= _mouseVoxel.s; } else { _nudgeGuidePosition.z += _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x += _mouseVoxel.s; } else { _nudgeGuidePosition.x -= _mouseVoxel.s; } } } else { _myAvatar.setDriveKeys(ROT_LEFT, 1); } break; case Qt::Key_D: if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z += _mouseVoxel.s; } else { _nudgeGuidePosition.z -= _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x -= _mouseVoxel.s; } else { _nudgeGuidePosition.x += _mouseVoxel.s; } } } else { _myAvatar.setDriveKeys(ROT_RIGHT, 1); } break; case Qt::Key_Return: case Qt::Key_Enter: if (_nudgeStarted) { nudgeVoxels(); } else { _chatEntryOn = true; _myAvatar.setKeyState(NO_KEY_DOWN); _myAvatar.setChatMessage(string()); setMenuShortcutsEnabled(false); } break; case Qt::Key_Up: if (_nudgeStarted && !isShifted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x += _mouseVoxel.s; } else { _nudgeGuidePosition.x -= _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z += _mouseVoxel.s; } else { _nudgeGuidePosition.z -= _mouseVoxel.s; } } } else if (_nudgeStarted && isShifted) { _nudgeGuidePosition.y += _mouseVoxel.s; } else { _myAvatar.setDriveKeys(isShifted ? UP : FWD, 1); } break; case Qt::Key_Down: if (_nudgeStarted && !isShifted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x -= _mouseVoxel.s; } else { _nudgeGuidePosition.x += _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z -= _mouseVoxel.s; } else { _nudgeGuidePosition.z += _mouseVoxel.s; } } } else if (_nudgeStarted && isShifted) { _nudgeGuidePosition.y -= _mouseVoxel.s; } else { _myAvatar.setDriveKeys(isShifted ? DOWN : BACK, 1); } break; case Qt::Key_Left: if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z -= _mouseVoxel.s; } else { _nudgeGuidePosition.z += _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x += _mouseVoxel.s; } else { _nudgeGuidePosition.x -= _mouseVoxel.s; } } } else { _myAvatar.setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1); } break; case Qt::Key_Right: if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.z += _mouseVoxel.s; } else { _nudgeGuidePosition.z -= _mouseVoxel.s; } } else { if (_lookingAwayFromOrigin) { _nudgeGuidePosition.x -= _mouseVoxel.s; } else { _nudgeGuidePosition.x += _mouseVoxel.s; } } } else { _myAvatar.setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1); } 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); } 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); } 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: Menu::getInstance()->triggerOption(MenuOption::Mirror); break; case Qt::Key_F: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::DisplayFrustum); } break; case Qt::Key_V: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Voxels); } else { Menu::getInstance()->triggerOption(MenuOption::VoxelAddMode); _nudgeStarted = false; } break; case Qt::Key_P: Menu::getInstance()->triggerOption(MenuOption::FirstPerson); break; case Qt::Key_R: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode); } else { Menu::getInstance()->triggerOption(MenuOption::VoxelDeleteMode); _nudgeStarted = false; } break; case Qt::Key_B: Menu::getInstance()->triggerOption(MenuOption::VoxelColorMode); _nudgeStarted = false; break; case Qt::Key_O: Menu::getInstance()->triggerOption(MenuOption::VoxelSelectMode); _nudgeStarted = false; break; case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; case Qt::Key_Backspace: case Qt::Key_Delete: if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) || Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) { deleteVoxelUnderCursor(); } break; case Qt::Key_Plus: _myAvatar.increaseSize(); break; case Qt::Key_Minus: _myAvatar.decreaseSize(); break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: _swatch.handleEvent(event->key(), Menu::getInstance()->isOptionChecked(MenuOption::VoxelGetColorMode)); break; default: event->ignore(); break; } } } void Application::keyReleaseEvent(QKeyEvent* event) { if (activeWindow() == _window) { if (_chatEntryOn) { _myAvatar.setKeyState(NO_KEY_DOWN); return; } switch (event->key()) { case Qt::Key_Shift: _pasteMode = false; break; case Qt::Key_E: _myAvatar.setDriveKeys(UP, 0); break; case Qt::Key_C: _myAvatar.setDriveKeys(DOWN, 0); break; case Qt::Key_W: _myAvatar.setDriveKeys(FWD, 0); break; case Qt::Key_S: _myAvatar.setDriveKeys(BACK, 0); break; case Qt::Key_A: _myAvatar.setDriveKeys(ROT_LEFT, 0); break; case Qt::Key_D: _myAvatar.setDriveKeys(ROT_RIGHT, 0); break; case Qt::Key_Up: _myAvatar.setDriveKeys(FWD, 0); _myAvatar.setDriveKeys(UP, 0); break; case Qt::Key_Down: _myAvatar.setDriveKeys(BACK, 0); _myAvatar.setDriveKeys(DOWN, 0); break; case Qt::Key_Left: _myAvatar.setDriveKeys(LEFT, 0); _myAvatar.setDriveKeys(ROT_LEFT, 0); break; case Qt::Key_Right: _myAvatar.setDriveKeys(RIGHT, 0); _myAvatar.setDriveKeys(ROT_RIGHT, 0); break; default: event->ignore(); break; } } } void Application::mouseMoveEvent(QMouseEvent* event) { if (activeWindow() == _window) { _mouseX = event->x(); _mouseY = event->y(); // detect drag glm::vec3 mouseVoxelPos(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z); if (!_justEditedVoxel && mouseVoxelPos != _lastMouseVoxelPos) { if (event->buttons().testFlag(Qt::LeftButton)) { maybeEditVoxelUnderCursor(); } else if (event->buttons().testFlag(Qt::RightButton) && Menu::getInstance()->isVoxelModeActionChecked()) { deleteVoxelUnderCursor(); } } _pieMenu.mouseMoveEvent(_mouseX, _mouseY); } } const bool MAKE_SOUND_ON_VOXEL_HOVER = false; const bool MAKE_SOUND_ON_VOXEL_CLICK = true; const float HOVER_VOXEL_FREQUENCY = 7040.f; const float HOVER_VOXEL_DECAY = 0.999f; void Application::mousePressEvent(QMouseEvent* event) { if (activeWindow() == _window) { if (event->button() == Qt::LeftButton) { _mouseX = event->x(); _mouseY = event->y(); _mouseDragStartedX = _mouseX; _mouseDragStartedY = _mouseY; _mouseVoxelDragging = _mouseVoxel; _mousePressed = true; maybeEditVoxelUnderCursor(); if (!_palette.isActive() && (!_isHoverVoxel || _lookatTargetAvatar)) { _pieMenu.mousePressEvent(_mouseX, _mouseY); } if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && _pasteMode) { pasteVoxels(); } if (MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) { _hoverVoxelOriginalColor[0] = _hoverVoxel.red; _hoverVoxelOriginalColor[1] = _hoverVoxel.green; _hoverVoxelOriginalColor[2] = _hoverVoxel.blue; _hoverVoxelOriginalColor[3] = 1; const float RED_CLICK_FREQUENCY = 1000.f; const float GREEN_CLICK_FREQUENCY = 1250.f; const float BLUE_CLICK_FREQUENCY = 1330.f; const float MIDDLE_A_FREQUENCY = 440.f; float frequency = MIDDLE_A_FREQUENCY + (_hoverVoxel.red / 255.f * RED_CLICK_FREQUENCY + _hoverVoxel.green / 255.f * GREEN_CLICK_FREQUENCY + _hoverVoxel.blue / 255.f * BLUE_CLICK_FREQUENCY) / 3.f; _audio.startCollisionSound(1.0, frequency, 0.0, HOVER_VOXEL_DECAY); _isHoverVoxelSounding = true; const float PERCENTAGE_TO_MOVE_TOWARD = 0.90f; glm::vec3 newTarget = getMouseVoxelWorldCoordinates(_hoverVoxel); glm::vec3 myPosition = _myAvatar.getPosition(); // If there is not an action tool set (add, delete, color), move to this voxel if (!(Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode) || Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) || Menu::getInstance()->isOptionChecked(MenuOption::VoxelColorMode))) { _myAvatar.setMoveTarget(myPosition + (newTarget - myPosition) * PERCENTAGE_TO_MOVE_TOWARD); } } } else if (event->button() == Qt::RightButton && Menu::getInstance()->isVoxelModeActionChecked()) { deleteVoxelUnderCursor(); } } } void Application::mouseReleaseEvent(QMouseEvent* event) { if (activeWindow() == _window) { if (event->button() == Qt::LeftButton) { _mouseX = event->x(); _mouseY = event->y(); _mousePressed = false; checkBandwidthMeterClick(); _pieMenu.mouseReleaseEvent(_mouseX, _mouseY); } } } void Application::touchUpdateEvent(QTouchEvent* event) { bool validTouch = false; if (activeWindow() == _window) { const QList& 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) { touchUpdateEvent(event); _lastTouchAvgX = _touchAvgX; _lastTouchAvgY = _touchAvgY; } void Application::touchEndEvent(QTouchEvent* event) { _touchDragStartedAvgX = _touchAvgX; _touchDragStartedAvgY = _touchAvgY; _isTouchPressed = false; } const bool USE_MOUSEWHEEL = false; void Application::wheelEvent(QWheelEvent* event) { // Wheel Events disabled for now because they are also activated by touch look pitch up/down. if (USE_MOUSEWHEEL && (activeWindow() == _window)) { if (!Menu::getInstance()->isVoxelModeActionChecked()) { event->ignore(); return; } if (event->delta() > 0) { increaseVoxelSize(); } else { decreaseVoxelSize(); } } } void Application::sendPingPackets() { const char nodesToPing[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER}; uint64_t currentTime = usecTimestampNow(); unsigned char pingPacket[numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_PING) + sizeof(currentTime)]; int numHeaderBytes = populateTypeAndVersion(pingPacket, PACKET_TYPE_PING); memcpy(pingPacket + numHeaderBytes, ¤tTime, sizeof(currentTime)); getInstance()->controlledBroadcastToNodes(pingPacket, sizeof(pingPacket), nodesToPing, sizeof(nodesToPing)); } void Application::sendAvatarFaceVideoMessage(int frameCount, const QByteArray& data) { unsigned char packet[MAX_PACKET_SIZE]; unsigned char* packetPosition = packet; packetPosition += populateTypeAndVersion(packetPosition, PACKET_TYPE_AVATAR_FACE_VIDEO); *(uint16_t*)packetPosition = NodeList::getInstance()->getOwnerID(); packetPosition += sizeof(uint16_t); *(uint32_t*)packetPosition = frameCount; packetPosition += sizeof(uint32_t); *(uint32_t*)packetPosition = data.size(); packetPosition += sizeof(uint32_t); uint32_t* offsetPosition = (uint32_t*)packetPosition; packetPosition += sizeof(uint32_t); int headerSize = packetPosition - packet; // break the data up into submessages of the maximum size (at least one, for zero-length packets) *offsetPosition = 0; do { int payloadSize = min(data.size() - (int)*offsetPosition, MAX_PACKET_SIZE - headerSize); memcpy(packetPosition, data.constData() + *offsetPosition, payloadSize); getInstance()->controlledBroadcastToNodes(packet, headerSize + payloadSize, &NODE_TYPE_AVATAR_MIXER, 1); *offsetPosition += payloadSize; } while (*offsetPosition < data.size()); } // Every second, check the frame rates and other stuff void Application::timer() { gettimeofday(&_timerEnd, NULL); if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { sendPingPackets(); } _fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _packetsPerSecond = (float)_packetCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _bytesPerSecond = (float)_bytesCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _frameCount = 0; _packetCount = 0; _bytesCount = 0; gettimeofday(&_timerStart, NULL); // if we haven't detected gyros, check for them now if (!_serialHeadSensor.isActive()) { _serialHeadSensor.pair(); } // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); } static glm::vec3 getFaceVector(BoxFace face) { switch (face) { case MIN_X_FACE: return glm::vec3(-1, 0, 0); case MAX_X_FACE: return glm::vec3(1, 0, 0); case MIN_Y_FACE: return glm::vec3(0, -1, 0); case MAX_Y_FACE: return glm::vec3(0, 1, 0); case MIN_Z_FACE: return glm::vec3(0, 0, -1); case MAX_Z_FACE: return glm::vec3(0, 0, 1); } } void Application::idle() { timeval check; gettimeofday(&check, NULL); // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran double timeSinceLastUpdate = diffclock(&_lastTimeUpdated, &check); if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) { const float BIGGEST_DELTA_TIME_SECS = 0.25f; update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS)); _glWidget->updateGL(); _lastTimeUpdated = check; _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(); } // After finishing all of the above work, restart the idle timer, allowing 2ms to process events. idleTimer->start(2); } } void Application::terminate() { // Close serial port // close(serial_fd); LeapManager::terminate(); Menu::getInstance()->saveSettings(); _settings->sync(); if (_enableNetworkThread) { _stopNetworkReceiveThread = true; pthread_join(_networkReceiveThread, NULL); } _voxelProcessor.terminate(); _voxelEditSender.terminate(); } static Avatar* processAvatarMessageHeader(unsigned char*& packetData, size_t& dataBytes) { // record the packet for stats-tracking Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AVATARS).updateValue(dataBytes); Node* avatarMixerNode = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); if (avatarMixerNode) { avatarMixerNode->recordBytesReceived(dataBytes); } // skip the header int numBytesPacketHeader = numBytesForPacketHeader(packetData); packetData += numBytesPacketHeader; dataBytes -= numBytesPacketHeader; // read the node id uint16_t nodeID = *(uint16_t*)packetData; packetData += sizeof(nodeID); dataBytes -= sizeof(nodeID); // make sure the node exists Node* node = NodeList::getInstance()->nodeWithID(nodeID); if (!node || !node->getLinkedData()) { return NULL; } Avatar* avatar = static_cast(node->getLinkedData()); return avatar->isInitialized() ? avatar : NULL; } void Application::processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes) { Avatar* avatar = processAvatarMessageHeader(packetData, dataBytes); if (!avatar) { return; } QDataStream in(QByteArray((char*)packetData, dataBytes)); QUrl voxelURL; in >> voxelURL; // invoke the set URL functions on the simulate/render thread QMetaObject::invokeMethod(avatar->getVoxels(), "setVoxelURL", Q_ARG(QUrl, voxelURL)); // use this timing to as the data-server for an updated mesh for this avatar (if we have UUID) DataServerClient::getValueForKeyAndUUID(DataServerKey::FaceMeshURL, avatar->getUUID()); } void Application::processAvatarFaceVideoMessage(unsigned char* packetData, size_t dataBytes) { Avatar* avatar = processAvatarMessageHeader(packetData, dataBytes); if (!avatar) { return; } avatar->getHead().getFace().processVideoMessage(packetData, dataBytes); } 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) { _window->setWindowState(fullscreen ? (_window->windowState() | Qt::WindowFullScreen) : (_window->windowState() & ~Qt::WindowFullScreen)); updateCursor(); } void Application::setRenderVoxels(bool voxelRender) { _voxelEditSender.setShouldSend(voxelRender); if (!voxelRender) { doKillLocalVoxels(); } } void Application::doKillLocalVoxels() { _wantToKillLocalVoxels = true; } const 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); } const float NUDGE_PRECISION_MIN = 1 / pow(2.0, 12.0); void Application::decreaseVoxelSize() { if (_nudgeStarted) { if (_mouseVoxelScale >= NUDGE_PRECISION_MIN) { _mouseVoxelScale /= 2; } } else { _mouseVoxelScale /= 2; } } void Application::increaseVoxelSize() { if (_nudgeStarted) { if (_mouseVoxelScale < _nudgeVoxel.s) { _mouseVoxelScale *= 2; } } else { _mouseVoxelScale *= 2; } } const int MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE = 1500; struct SendVoxelsOperationArgs { unsigned char* newBaseOctCode; }; bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData; if (node->isColored()) { unsigned char* nodeOctalCode = node->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 ] = node->getColor()[RED_INDEX ]; codeColorBuffer[bytesInCode + GREEN_INDEX] = node->getColor()[GREEN_INDEX]; codeColorBuffer[bytesInCode + BLUE_INDEX ] = node->getColor()[BLUE_INDEX ]; getInstance()->_voxelEditSender.queueVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE, codeColorBuffer, codeAndColorLength); delete[] codeColorBuffer; } return true; // keep going } void Application::exportVoxels() { 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(); VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode) { VoxelTree exportTree; _voxels.copySubTreeIntoNewTree(selectedNode, &exportTree, true); exportTree.writeToSVOFile(fileName); } // restore the main window's active state _window->activateWindow(); } void Application::importVoxels() { if (_voxelImporter.exec()) { qDebug("[DEBUG] Import succedded.\n"); } else { qDebug("[DEBUG] Import failed.\n"); } // restore the main window's active state _window->activateWindow(); } void Application::cutVoxels() { copyVoxels(); deleteVoxelUnderCursor(); } void Application::copyVoxels() { // switch to and clear the clipboard first... _sharedVoxelSystem.killLocalVoxels(); if (_sharedVoxelSystem.getTree() != &_clipboard) { _clipboard.eraseAllVoxels(); _sharedVoxelSystem.changeTree(&_clipboard); } // then copy onto it if there is something to copy VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode) { _voxels.copySubTreeIntoNewTree(selectedNode, &_sharedVoxelSystem, true); } } void Application::pasteVoxels() { unsigned char* calculatedOctCode = NULL; VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); // 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; // 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. if (selectedNode) { args.newBaseOctCode = selectedNode->getOctalCode(); } else { args.newBaseOctCode = calculatedOctCode = pointToVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); } _sharedVoxelSystem.getTree()->recurseTreeWithOperation(sendVoxelsOperation, &args); if (_sharedVoxelSystem.getTree() != &_clipboard) { _sharedVoxelSystem.killLocalVoxels(); _sharedVoxelSystem.changeTree(&_clipboard); } _voxelEditSender.releaseQueuedMessages(); if (calculatedOctCode) { delete[] calculatedOctCode; } } void Application::findAxisAlignment() { glm::vec3 direction = _myAvatar.getMouseRayDirection(); if (fabs(direction.z) > fabs(direction.x)) { _lookingAlongX = false; if (direction.z < 0) { _lookingAwayFromOrigin = false; } else { _lookingAwayFromOrigin = true; } } else { _lookingAlongX = true; if (direction.x < 0) { _lookingAwayFromOrigin = false; } else { _lookingAwayFromOrigin = true; } } } void Application::nudgeVoxels() { VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (!Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && selectedNode) { Menu::getInstance()->triggerOption(MenuOption::VoxelSelectMode); } if (!_nudgeStarted && selectedNode) { _nudgeVoxel = _mouseVoxel; _nudgeStarted = true; _nudgeGuidePosition = glm::vec3(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z); findAxisAlignment(); } else { // calculate nudgeVec glm::vec3 nudgeVec(_nudgeGuidePosition.x - _nudgeVoxel.x, _nudgeGuidePosition.y - _nudgeVoxel.y, _nudgeGuidePosition.z - _nudgeVoxel.z); VoxelNode* nodeToNudge = _voxels.getVoxelAt(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s); if (nodeToNudge) { _voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender); _nudgeStarted = false; } } } void Application::deleteVoxels() { deleteVoxelUnderCursor(); } void Application::setListenModeNormal() { _audio.setListenMode(AudioRingBuffer::NORMAL); } void Application::setListenModePoint() { _audio.setListenMode(AudioRingBuffer::OMNI_DIRECTIONAL_POINT); _audio.setListenRadius(1.0); } void Application::setListenModeSingleSource() { _audio.setListenMode(AudioRingBuffer::SELECTED_SOURCES); _audio.clearListenSources(); glm::vec3 mouseRayOrigin = _myAvatar.getMouseRayOrigin(); glm::vec3 mouseRayDirection = _myAvatar.getMouseRayDirection(); glm::vec3 eyePositionIgnored; uint16_t nodeID; if (findLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, eyePositionIgnored, nodeID)) { _audio.addListenSource(nodeID); } } 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); _sharedVoxelSystemViewFrustum.setOrientation(glm::quat()); _sharedVoxelSystemViewFrustum.calculate(); _sharedVoxelSystem.setViewFrustum(&_sharedVoxelSystemViewFrustum); VoxelNode::removeUpdateHook(&_sharedVoxelSystem); _sharedVoxelSystem.init(); VoxelTree* tmpTree = _sharedVoxelSystem.getTree(); _sharedVoxelSystem.changeTree(&_clipboard); delete tmpTree; _voxelImporter.init(); _environment.init(); _glowEffect.init(); _ambientOcclusionEffect.init(); _voxelShader.init(); _handControl.setScreenDimensions(_glWidget->width(), _glWidget->height()); _headMouseX = _mouseX = _glWidget->width() / 2; _headMouseY = _mouseY = _glWidget->height() / 2; QCursor::setPos(_headMouseX, _headMouseY); _myAvatar.init(); _myAvatar.setPosition(START_LOCATION); _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); _myCamera.setModeShiftRate(1.0f); _myAvatar.setDisplayingLookatVectors(false); OculusManager::connect(); if (OculusManager::isConnected()) { QMetaObject::invokeMethod(Menu::getInstance()->getActionForOption(MenuOption::Fullscreen), "trigger", Qt::QueuedConnection); } LeapManager::initialize(); gettimeofday(&_timerStart, NULL); gettimeofday(&_lastTimeUpdated, NULL); Menu::getInstance()->loadSettings(); if (Menu::getInstance()->getAudioJitterBufferSamples() != 0) { _audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples()); } qDebug("Loaded settings.\n"); if (!_profile.getUsername().isEmpty()) { // we have a username for this avatar, ask the data-server for the mesh URL for this avatar DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL); } // Set up VoxelSystem after loading preferences so we can get the desired max voxel count _voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels()); _voxels.setUseVoxelShader(Menu::getInstance()->isOptionChecked(MenuOption::UseVoxelShader)); _voxels.setVoxelsAsPoints(Menu::getInstance()->isOptionChecked(MenuOption::VoxelsAsPoints)); _voxels.setUseFastVoxelPipeline(Menu::getInstance()->isOptionChecked(MenuOption::FastVoxelPipeline)); _voxels.init(); Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL()); _palette.init(_glWidget->width(), _glWidget->height()); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelDeleteMode), 0, 1); _palette.addTool(&_swatch); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelColorMode), 0, 2); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelGetColorMode), 0, 3); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelSelectMode), 0, 4); _pieMenu.init("./resources/images/hifi-interface-tools-v2-pie.svg", _glWidget->width(), _glWidget->height()); _followMode = new QAction(this); connect(_followMode, SIGNAL(triggered()), this, SLOT(toggleFollowMode())); _pieMenu.addAction(_followMode); } const float MAX_AVATAR_EDIT_VELOCITY = 1.0f; const float MAX_VOXEL_EDIT_DISTANCE = 20.0f; const float HEAD_SPHERE_RADIUS = 0.07; static uint16_t DEFAULT_NODE_ID_REF = 1; void Application::updateLookatTargetAvatar(const glm::vec3& mouseRayOrigin, const glm::vec3& mouseRayDirection, glm::vec3& eyePosition) { _lookatTargetAvatar = findLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, eyePosition, DEFAULT_NODE_ID_REF); } Avatar* Application::findLookatTargetAvatar(const glm::vec3& mouseRayOrigin, const glm::vec3& mouseRayDirection, glm::vec3& eyePosition, uint16_t& nodeID = DEFAULT_NODE_ID_REF) { NodeList* nodeList = NodeList::getInstance(); for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { Avatar* avatar = (Avatar *) node->getLinkedData(); glm::vec3 headPosition = avatar->getHead().getPosition(); float distance; if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition, HEAD_SPHERE_RADIUS * avatar->getHead().getScale(), distance)) { eyePosition = avatar->getHead().getEyePosition(); _lookatIndicatorScale = avatar->getHead().getScale(); _lookatOtherPosition = headPosition; nodeID = avatar->getOwningNode()->getNodeID(); return avatar; } } } return NULL; } bool Application::isLookingAtMyAvatar(Avatar* avatar) { glm::vec3 theirLookat = avatar->getHead().getLookAtPosition(); glm::vec3 myHeadPosition = _myAvatar.getHead().getPosition(); if (pointInSphere(theirLookat, myHeadPosition, HEAD_SPHERE_RADIUS * _myAvatar.getScale())) { return true; } return false; } void Application::renderLookatIndicator(glm::vec3 pointOfInterest, Camera& whichCamera) { const float DISTANCE_FROM_HEAD_SPHERE = 0.1f * _lookatIndicatorScale; const float INDICATOR_RADIUS = 0.1f * _lookatIndicatorScale; const float YELLOW[] = { 1.0f, 1.0f, 0.0f }; const int NUM_SEGMENTS = 30; glm::vec3 haloOrigin(pointOfInterest.x, pointOfInterest.y + DISTANCE_FROM_HEAD_SPHERE, pointOfInterest.z); glColor3f(YELLOW[0], YELLOW[1], YELLOW[2]); renderCircle(haloOrigin, INDICATOR_RADIUS, IDENTITY_UP, NUM_SEGMENTS); } void maybeBeginFollowIndicator(bool& began) { if (!began) { Application::getInstance()->getGlowEffect()->begin(); glLineWidth(5); glBegin(GL_LINES); began = true; } } void Application::renderFollowIndicator() { NodeList* nodeList = NodeList::getInstance(); // initialize lazily so that we don't enable the glow effect unnecessarily bool began = false; for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); ++node) { if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { Avatar* avatar = (Avatar *) node->getLinkedData(); Avatar* leader = NULL; if (avatar->getLeaderID() != UNKNOWN_NODE_ID) { if (avatar->getLeaderID() == NodeList::getInstance()->getOwnerID()) { leader = &_myAvatar; } else { for (NodeList::iterator it = nodeList->begin(); it != nodeList->end(); ++it) { if(it->getNodeID() == avatar->getLeaderID() && it->getType() == NODE_TYPE_AGENT) { leader = (Avatar*) it->getLinkedData(); } } } if (leader != NULL) { maybeBeginFollowIndicator(began); glColor3f(1.f, 0.f, 0.f); glVertex3f((avatar->getHead().getPosition().x + avatar->getPosition().x) / 2.f, (avatar->getHead().getPosition().y + avatar->getPosition().y) / 2.f, (avatar->getHead().getPosition().z + avatar->getPosition().z) / 2.f); glColor3f(0.f, 1.f, 0.f); glVertex3f((leader->getHead().getPosition().x + leader->getPosition().x) / 2.f, (leader->getHead().getPosition().y + leader->getPosition().y) / 2.f, (leader->getHead().getPosition().z + leader->getPosition().z) / 2.f); } } } } if (_myAvatar.getLeadingAvatar() != NULL) { maybeBeginFollowIndicator(began); glColor3f(1.f, 0.f, 0.f); glVertex3f((_myAvatar.getHead().getPosition().x + _myAvatar.getPosition().x) / 2.f, (_myAvatar.getHead().getPosition().y + _myAvatar.getPosition().y) / 2.f, (_myAvatar.getHead().getPosition().z + _myAvatar.getPosition().z) / 2.f); glColor3f(0.f, 1.f, 0.f); glVertex3f((_myAvatar.getLeadingAvatar()->getHead().getPosition().x + _myAvatar.getLeadingAvatar()->getPosition().x) / 2.f, (_myAvatar.getLeadingAvatar()->getHead().getPosition().y + _myAvatar.getLeadingAvatar()->getPosition().y) / 2.f, (_myAvatar.getLeadingAvatar()->getHead().getPosition().z + _myAvatar.getLeadingAvatar()->getPosition().z) / 2.f); } if (began) { glEnd(); _glowEffect.end(); } } void Application::update(float deltaTime) { // Use Transmitter Hand to move hand if connected, else use mouse if (_myTransmitter.isConnected()) { const float HAND_FORCE_SCALING = 0.01f; glm::vec3 estimatedRotation = _myTransmitter.getEstimatedRotation(); glm::vec3 handForce(-estimatedRotation.z, -estimatedRotation.x, estimatedRotation.y); _myAvatar.setMovedHandOffset(handForce * HAND_FORCE_SCALING); } else { // update behaviors for avatar hand movement: handControl takes mouse values as input, // and gives back 3D values modulated for smooth transitioning between interaction modes. _handControl.update(_mouseX, _mouseY); _myAvatar.setMovedHandOffset(_handControl.getValues()); } // tell my avatar if the mouse is being pressed... _myAvatar.setMousePressed(_mousePressed); // check what's under the mouse and update the mouse voxel glm::vec3 mouseRayOrigin, mouseRayDirection; _viewFrustum.computePickRay(_mouseX / (float)_glWidget->width(), _mouseY / (float)_glWidget->height(), mouseRayOrigin, mouseRayDirection); // tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position _myAvatar.setMouseRay(mouseRayOrigin, mouseRayDirection); // Set where I am looking based on my mouse ray (so that other people can see) glm::vec3 lookAtSpot; // Update faceshift _faceshift.update(); // Copy angular velocity if measured by faceshift, to the head if (_faceshift.isActive()) { _myAvatar.getHead().setAngularVelocity(_faceshift.getHeadAngularVelocity()); } // if we have faceshift, use that to compute the lookat direction glm::vec3 lookAtRayOrigin = mouseRayOrigin, lookAtRayDirection = mouseRayDirection; if (_faceshift.isActive()) { lookAtRayOrigin = _myAvatar.getHead().calculateAverageEyePosition(); lookAtRayDirection = _myAvatar.getHead().getOrientation() * glm::quat(glm::radians(glm::vec3( _faceshift.getEstimatedEyePitch(), _faceshift.getEstimatedEyeYaw(), 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f); } updateLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, lookAtSpot); if (_lookatTargetAvatar && !_faceshift.isActive()) { // If the mouse is over another avatar's head... _myAvatar.getHead().setLookAtPosition(lookAtSpot); } else if (_isHoverVoxel && !_faceshift.isActive()) { // Look at the hovered voxel lookAtSpot = getMouseVoxelWorldCoordinates(_hoverVoxel); _myAvatar.getHead().setLookAtPosition(lookAtSpot); } else { // Just look in direction of the mouse ray const float FAR_AWAY_STARE = TREE_SCALE; lookAtSpot = lookAtRayOrigin + lookAtRayDirection * FAR_AWAY_STARE; _myAvatar.getHead().setLookAtPosition(lookAtSpot); } // Find the voxel we are hovering over, and respond if clicked float distance; BoxFace face; // If we have clicked on a voxel, update it's color if (_isHoverVoxelSounding) { VoxelNode* hoveredNode = _voxels.getVoxelAt(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s); if (hoveredNode) { float bright = _audio.getCollisionSoundMagnitude(); nodeColor clickColor = { 255 * bright + _hoverVoxelOriginalColor[0] * (1.f - bright), _hoverVoxelOriginalColor[1] * (1.f - bright), _hoverVoxelOriginalColor[2] * (1.f - bright), 1 }; hoveredNode->setColor(clickColor); if (bright < 0.01f) { hoveredNode->setColor(_hoverVoxelOriginalColor); _isHoverVoxelSounding = false; } } else { // Voxel is not found, clear all _isHoverVoxelSounding = false; _isHoverVoxel = false; } } else { // Check for a new hover voxel glm::vec4 oldVoxel(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s); _isHoverVoxel = _voxels.findRayIntersection(mouseRayOrigin, mouseRayDirection, _hoverVoxel, distance, face); if (MAKE_SOUND_ON_VOXEL_HOVER && _isHoverVoxel && glm::vec4(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s) != oldVoxel) { _hoverVoxelOriginalColor[0] = _hoverVoxel.red; _hoverVoxelOriginalColor[1] = _hoverVoxel.green; _hoverVoxelOriginalColor[2] = _hoverVoxel.blue; _hoverVoxelOriginalColor[3] = 1; _audio.startCollisionSound(1.0, HOVER_VOXEL_FREQUENCY * _hoverVoxel.s * TREE_SCALE, 0.0, HOVER_VOXEL_DECAY); _isHoverVoxelSounding = true; } } _mouseVoxel.s = 0.0f; if (Menu::getInstance()->isVoxelModeActionChecked() && (fabs(_myAvatar.getVelocity().x) + fabs(_myAvatar.getVelocity().y) + fabs(_myAvatar.getVelocity().z)) / 3 < MAX_AVATAR_EDIT_VELOCITY) { if (_voxels.findRayIntersection(mouseRayOrigin, mouseRayDirection, _mouseVoxel, distance, face)) { if (distance < MAX_VOXEL_EDIT_DISTANCE) { // find the nearest voxel with the desired scale if (_mouseVoxelScale > _mouseVoxel.s) { // choose the larger voxel that encompasses the one selected _mouseVoxel.x = _mouseVoxelScale * floorf(_mouseVoxel.x / _mouseVoxelScale); _mouseVoxel.y = _mouseVoxelScale * floorf(_mouseVoxel.y / _mouseVoxelScale); _mouseVoxel.z = _mouseVoxelScale * floorf(_mouseVoxel.z / _mouseVoxelScale); _mouseVoxel.s = _mouseVoxelScale; } else { glm::vec3 faceVector = getFaceVector(face); if (_mouseVoxelScale < _mouseVoxel.s) { // find the closest contained voxel glm::vec3 pt = (mouseRayOrigin + mouseRayDirection * distance) / (float)TREE_SCALE - faceVector * (_mouseVoxelScale * 0.5f); _mouseVoxel.x = _mouseVoxelScale * floorf(pt.x / _mouseVoxelScale); _mouseVoxel.y = _mouseVoxelScale * floorf(pt.y / _mouseVoxelScale); _mouseVoxel.z = _mouseVoxelScale * floorf(pt.z / _mouseVoxelScale); _mouseVoxel.s = _mouseVoxelScale; } if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode)) { // use the face to determine the side on which to create a neighbor _mouseVoxel.x += faceVector.x * _mouseVoxel.s; _mouseVoxel.y += faceVector.y * _mouseVoxel.s; _mouseVoxel.z += faceVector.z * _mouseVoxel.s; } } } else { _mouseVoxel.s = 0.0f; } } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode) || Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) { // place the voxel a fixed distance away float worldMouseVoxelScale = _mouseVoxelScale * TREE_SCALE; glm::vec3 pt = mouseRayOrigin + mouseRayDirection * (2.0f + worldMouseVoxelScale * 0.5f); _mouseVoxel.x = _mouseVoxelScale * floorf(pt.x / worldMouseVoxelScale); _mouseVoxel.y = _mouseVoxelScale * floorf(pt.y / worldMouseVoxelScale); _mouseVoxel.z = _mouseVoxelScale * floorf(pt.z / worldMouseVoxelScale); _mouseVoxel.s = _mouseVoxelScale; } if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode)) { // red indicates deletion _mouseVoxel.red = 255; _mouseVoxel.green = _mouseVoxel.blue = 0; } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) { if (_nudgeStarted) { _mouseVoxel.red = _mouseVoxel.green = _mouseVoxel.blue = 255; } else { // yellow indicates selection _mouseVoxel.red = _mouseVoxel.green = 255; _mouseVoxel.blue = 0; } } else { // _addVoxelMode->isChecked() || _colorVoxelMode->isChecked() QColor paintColor = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor)->data().value(); _mouseVoxel.red = paintColor.red(); _mouseVoxel.green = paintColor.green(); _mouseVoxel.blue = paintColor.blue(); } // if we just edited, use the currently selected voxel as the "last" for drag detection if (_justEditedVoxel) { _lastMouseVoxelPos = glm::vec3(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z); _justEditedVoxel = false; } } // walking triggers the handControl to stop if (_myAvatar.getMode() == AVATAR_MODE_WALKING) { _handControl.stop(); } // Update from Touch if (_isTouchPressed) { float TOUCH_YAW_SCALE = -0.25f; float TOUCH_PITCH_SCALE = -12.5f; float FIXED_TOUCH_TIMESTEP = 0.016f; _yawFromTouch += ((_touchAvgX - _lastTouchAvgX) * TOUCH_YAW_SCALE * FIXED_TOUCH_TIMESTEP); _pitchFromTouch += ((_touchAvgY - _lastTouchAvgY) * TOUCH_PITCH_SCALE * FIXED_TOUCH_TIMESTEP); _lastTouchAvgX = _touchAvgX; _lastTouchAvgY = _touchAvgY; } // Leap finger-sensing device LeapManager::enableFakeFingers(Menu::getInstance()->isOptionChecked(MenuOption::SimulateLeapHand)); _myAvatar.getHand().setRaveGloveActive(Menu::getInstance()->isOptionChecked(MenuOption::TestRaveGlove)); LeapManager::nextFrame(_myAvatar); // Read serial port interface devices if (_serialHeadSensor.isActive()) { _serialHeadSensor.readData(deltaTime); } // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes updateAvatar(deltaTime); // read incoming packets from network if (!_enableNetworkThread) { networkReceive(0); } // parse voxel packets if (!_enableProcessVoxelsThread) { _voxelProcessor.threadRoutine(); _voxelEditSender.threadRoutine(); } //loop through all the other avatars and simulate them... NodeList* nodeList = NodeList::getInstance(); for(NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { node->lock(); if (node->getLinkedData() != NULL) { Avatar *avatar = (Avatar *)node->getLinkedData(); if (!avatar->isInitialized()) { avatar->init(); } avatar->simulate(deltaTime, NULL); avatar->setMouseRay(mouseRayOrigin, mouseRayDirection); } node->unlock(); } // Simulate myself if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) { _myAvatar.setGravity(_environment.getGravity(_myAvatar.getPosition())); } else { _myAvatar.setGravity(glm::vec3(0.0f, 0.0f, 0.0f)); } if (Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) { _myAvatar.simulate(deltaTime, &_myTransmitter); } else { _myAvatar.simulate(deltaTime, NULL); } // Simulate particle cloud movements _cloud.simulate(deltaTime); // no transmitter drive implies transmitter pick if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) { _transmitterPickStart = _myAvatar.getSkeleton().joint[AVATAR_JOINT_CHEST].position; glm::vec3 direction = _myAvatar.getOrientation() * glm::quat(glm::radians(_myTransmitter.getEstimatedRotation())) * IDENTITY_FRONT; // check against voxels, avatars const float MAX_PICK_DISTANCE = 100.0f; float minDistance = MAX_PICK_DISTANCE; VoxelDetail detail; float distance; BoxFace face; if (_voxels.findRayIntersection(_transmitterPickStart, direction, detail, distance, face)) { minDistance = min(minDistance, distance); } for(NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { node->lock(); if (node->getLinkedData() != NULL) { Avatar *avatar = (Avatar*)node->getLinkedData(); if (!avatar->isInitialized()) { avatar->init(); } if (avatar->findRayIntersection(_transmitterPickStart, direction, distance)) { minDistance = min(minDistance, distance); } } node->unlock(); } _transmitterPickEnd = _transmitterPickStart + direction * minDistance; } else { _transmitterPickStart = _transmitterPickEnd = glm::vec3(); } if (!OculusManager::isConnected()) { if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { _myCamera.setMode(CAMERA_MODE_MIRROR); _myCamera.setModeShiftRate(100.0f); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); _myCamera.setModeShiftRate(1.0f); } } else { if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); _myCamera.setModeShiftRate(1.0f); } } if (Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { float xSign = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? 1.0f : -1.0f; if (_faceshift.isActive()) { const float EYE_OFFSET_SCALE = 0.025f; glm::vec3 position = _faceshift.getHeadTranslation() * EYE_OFFSET_SCALE; _myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, position.z)); updateProjectionMatrix(); } else if (_webcam.isActive()) { const float EYE_OFFSET_SCALE = 0.5f; glm::vec3 position = _webcam.getEstimatedPosition() * EYE_OFFSET_SCALE; _myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, -position.y, position.z)); updateProjectionMatrix(); } } } // Update bandwidth dialog, if any BandwidthDialog* bandwidthDialog = Menu::getInstance()->getBandwidthDialog(); if (bandwidthDialog) { bandwidthDialog->update(); } VoxelStatsDialog* voxelStatsDialog = Menu::getInstance()->getVoxelStatsDialog(); if (voxelStatsDialog) { voxelStatsDialog->update(); } // Update audio stats for procedural sounds #ifndef _WIN32 _audio.setLastAcceleration(_myAvatar.getThrust()); _audio.setLastVelocity(_myAvatar.getVelocity()); _audio.eventuallyAnalyzePing(); #endif } void Application::updateAvatar(float deltaTime) { // rotate body yaw for yaw received from multitouch _myAvatar.setOrientation(_myAvatar.getOrientation() * glm::quat(glm::vec3(0, _yawFromTouch, 0))); _yawFromTouch = 0.f; // Update my avatar's state from gyros and/or webcam _myAvatar.updateFromGyrosAndOrWebcam(Menu::getInstance()->isOptionChecked(MenuOption::GyroLook), _pitchFromTouch); // Update head mouse from faceshift if active if (_faceshift.isActive()) { glm::vec3 headVelocity = _faceshift.getHeadAngularVelocity(); // sets how quickly head angular rotation moves the head mouse const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.f; const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.f; _headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE; _headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE; } if (_serialHeadSensor.isActive()) { // Grab latest readings from the gyros float measuredPitchRate = _serialHeadSensor.getLastPitchRate(); float measuredYawRate = _serialHeadSensor.getLastYawRate(); // Update gyro-based mouse (X,Y on screen) const float MIN_MOUSE_RATE = 3.0; const float HORIZONTAL_PIXELS_PER_DEGREE = 2880.f / 45.f; const float VERTICAL_PIXELS_PER_DEGREE = 1800.f / 30.f; if (powf(measuredYawRate * measuredYawRate + measuredPitchRate * measuredPitchRate, 0.5) > MIN_MOUSE_RATE) { _headMouseX -= measuredYawRate * HORIZONTAL_PIXELS_PER_DEGREE * deltaTime; _headMouseY -= measuredPitchRate * VERTICAL_PIXELS_PER_DEGREE * deltaTime; } const float MIDPOINT_OF_SCREEN = 0.5; // Only use gyro to set lookAt if mouse hasn't selected an avatar if (!_lookatTargetAvatar) { // Set lookAtPosition if an avatar is at the center of the screen glm::vec3 screenCenterRayOrigin, screenCenterRayDirection; _viewFrustum.computePickRay(MIDPOINT_OF_SCREEN, MIDPOINT_OF_SCREEN, screenCenterRayOrigin, screenCenterRayDirection); glm::vec3 eyePosition; updateLookatTargetAvatar(screenCenterRayOrigin, screenCenterRayDirection, eyePosition); if (_lookatTargetAvatar) { glm::vec3 myLookAtFromMouse(eyePosition); _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); } } } // Constrain head-driven mouse to edges of screen _headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width()); _headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height()); if (OculusManager::isConnected()) { float yaw, pitch, roll; OculusManager::getEulerAngles(yaw, pitch, roll); _myAvatar.getHead().setYaw(yaw + _yawFromTouch); _myAvatar.getHead().setPitch(pitch + _pitchFromTouch); _myAvatar.getHead().setRoll(roll); } // Get audio loudness data from audio input device #ifndef _WIN32 _myAvatar.getHead().setAudioLoudness(_audio.getLastInputLoudness()); #endif // Update Avatar 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. loadViewFrustum(_myCamera, _viewFrustum); _myAvatar.setCameraPosition(_viewFrustum.getPosition()); _myAvatar.setCameraOrientation(_viewFrustum.getOrientation()); _myAvatar.setCameraFov(_viewFrustum.getFieldOfView()); _myAvatar.setCameraAspectRatio(_viewFrustum.getAspectRatio()); _myAvatar.setCameraNearClip(_viewFrustum.getNearClip()); _myAvatar.setCameraFarClip(_viewFrustum.getFarClip()); _myAvatar.setCameraEyeOffsetPosition(_viewFrustum.getEyeOffsetPosition()); NodeList* nodeList = NodeList::getInstance(); if (nodeList->getOwnerID() != UNKNOWN_NODE_ID) { // if I know my ID, send head/hand data to the avatar mixer and voxel server unsigned char broadcastString[MAX_PACKET_SIZE]; unsigned char* endOfBroadcastStringWrite = broadcastString; endOfBroadcastStringWrite += populateTypeAndVersion(endOfBroadcastStringWrite, PACKET_TYPE_HEAD_DATA); endOfBroadcastStringWrite += packNodeId(endOfBroadcastStringWrite, nodeList->getOwnerID()); endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite); const char nodeTypesOfInterest[] = { NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AVATAR_MIXER }; controlledBroadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString, nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); // once in a while, send my urls const float AVATAR_URLS_SEND_INTERVAL = 1.0f; // seconds if (shouldDo(AVATAR_URLS_SEND_INTERVAL, deltaTime)) { Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL()); } } } ///////////////////////////////////////////////////////////////////////////////////// // 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(); 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); viewFrustum.setNearClip(nearClip); viewFrustum.setFarClip(farClip); viewFrustum.setEyeOffsetPosition(camera.getEyeOffsetPosition()); viewFrustum.setEyeOffsetOrientation(camera.getEyeOffsetOrientation()); // Ask the ViewFrustum class to calculate our corners viewFrustum.calculate(); } // this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample // code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license // (http://www.apache.org/licenses/LICENSE-2.0) static const char* DISTORTION_FRAGMENT_SHADER = "#version 120\n" "uniform sampler2D texture;" "uniform vec2 lensCenter;" "uniform vec2 screenCenter;" "uniform vec2 scale;" "uniform vec2 scaleIn;" "uniform vec4 hmdWarpParam;" "vec2 hmdWarp(vec2 in01) {" " vec2 theta = (in01 - lensCenter) * scaleIn;" " float rSq = theta.x * theta.x + theta.y * theta.y;" " vec2 theta1 = theta * (hmdWarpParam.x + hmdWarpParam.y * rSq + " " hmdWarpParam.z * rSq * rSq + hmdWarpParam.w * rSq * rSq * rSq);" " return lensCenter + scale * theta1;" "}" "void main(void) {" " vec2 tc = hmdWarp(gl_TexCoord[0].st);" " vec2 below = step(screenCenter.st + vec2(-0.25, -0.5), tc.st);" " vec2 above = vec2(1.0, 1.0) - step(screenCenter.st + vec2(0.25, 0.5), tc.st);" " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texture2D(texture, tc), " " above.s * above.t * below.s * below.t);" "}"; void Application::displayOculus(Camera& whichCamera) { _glowEffect.prepare(); // magic numbers ahoy! in order to avoid pulling in the Oculus utility library that calculates // the rendering parameters from the hardware stats, i just folded their calculations into // constants using the stats for the current-model hardware as contained in the SDK file // LibOVR/Src/Util/Util_Render_Stereo.cpp // eye // render the left eye view to the left side of the screen glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glTranslatef(0.151976, 0, 0); // +h, see Oculus SDK docs p. 26 gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), whichCamera.getNearClip(), whichCamera.getFarClip()); glViewport(0, 0, _glWidget->width() / 2, _glWidget->height()); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glTranslatef(0.032, 0, 0); // dip/2, see p. 27 displaySide(whichCamera); // and the right eye to the right side glMatrixMode(GL_PROJECTION); glLoadIdentity(); glTranslatef(-0.151976, 0, 0); // -h gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), whichCamera.getNearClip(), whichCamera.getFarClip()); glViewport(_glWidget->width() / 2, 0, _glWidget->width() / 2, _glWidget->height()); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(-0.032, 0, 0); displaySide(whichCamera); glPopMatrix(); // restore our normal viewport glViewport(0, 0, _glWidget->width(), _glWidget->height()); QOpenGLFramebufferObject* fbo = _glowEffect.render(true); glBindTexture(GL_TEXTURE_2D, fbo->texture()); if (_oculusProgram == 0) { _oculusProgram = new ProgramObject(); _oculusProgram->addShaderFromSourceCode(QGLShader::Fragment, DISTORTION_FRAGMENT_SHADER); _oculusProgram->link(); _textureLocation = _oculusProgram->uniformLocation("texture"); _lensCenterLocation = _oculusProgram->uniformLocation("lensCenter"); _screenCenterLocation = _oculusProgram->uniformLocation("screenCenter"); _scaleLocation = _oculusProgram->uniformLocation("scale"); _scaleInLocation = _oculusProgram->uniformLocation("scaleIn"); _hmdWarpParamLocation = _oculusProgram->uniformLocation("hmdWarpParam"); } glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, _glWidget->width(), 0, _glWidget->height()); glDisable(GL_DEPTH_TEST); // for reference on setting these values, see SDK file Samples/OculusRoomTiny/RenderTiny_Device.cpp float scaleFactor = 1.0 / _oculusDistortionScale; float aspectRatio = (_glWidget->width() * 0.5) / _glWidget->height(); glDisable(GL_BLEND); _oculusProgram->bind(); _oculusProgram->setUniformValue(_textureLocation, 0); _oculusProgram->setUniformValue(_lensCenterLocation, 0.287994, 0.5); // see SDK docs, p. 29 _oculusProgram->setUniformValue(_screenCenterLocation, 0.25, 0.5); _oculusProgram->setUniformValue(_scaleLocation, 0.25 * scaleFactor, 0.5 * scaleFactor * aspectRatio); _oculusProgram->setUniformValue(_scaleInLocation, 4, 2 / aspectRatio); _oculusProgram->setUniformValue(_hmdWarpParamLocation, 1.0, 0.22, 0.24, 0); glColor3f(1, 0, 1); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(0, 0); glTexCoord2f(0.5, 0); glVertex2f(_glWidget->width()/2, 0); glTexCoord2f(0.5, 1); glVertex2f(_glWidget->width() / 2, _glWidget->height()); glTexCoord2f(0, 1); glVertex2f(0, _glWidget->height()); glEnd(); _oculusProgram->setUniformValue(_lensCenterLocation, 0.787994, 0.5); _oculusProgram->setUniformValue(_screenCenterLocation, 0.75, 0.5); glBegin(GL_QUADS); glTexCoord2f(0.5, 0); glVertex2f(_glWidget->width() / 2, 0); glTexCoord2f(1, 0); glVertex2f(_glWidget->width(), 0); glTexCoord2f(1, 1); glVertex2f(_glWidget->width(), _glWidget->height()); glTexCoord2f(0.5, 1); glVertex2f(_glWidget->width() / 2, _glWidget->height()); glEnd(); glEnable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, 0); _oculusProgram->release(); glPopMatrix(); } const GLfloat WHITE_SPECULAR_COLOR[] = { 1.0f, 1.0f, 1.0f, 1.0f }; const GLfloat NO_SPECULAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f }; void Application::setupWorldLight(Camera& whichCamera) { // 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 relativeSunLoc = glm::normalize(_environment.getClosestData(whichCamera.getPosition()).getSunLocation() - whichCamera.getPosition()); GLfloat light_position0[] = { relativeSunLoc.x, relativeSunLoc.y, relativeSunLoc.z, 0.0 }; glLightfv(GL_LIGHT0, GL_POSITION, light_position0); GLfloat ambient_color[] = { 0.7, 0.7, 0.8 }; glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color); GLfloat diffuse_color[] = { 0.8, 0.7, 0.7 }; glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color); glLightfv(GL_LIGHT0, GL_SPECULAR, WHITE_SPECULAR_COLOR); glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR); glMateriali(GL_FRONT, GL_SHININESS, 96); } void Application::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& near, float& far, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const { _viewFrustum.computeOffAxisFrustum(left, right, bottom, top, near, far, nearClipPlane, farClipPlane); // when mirrored, we must flip left and right if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { float tmp = left; left = -right; right = -tmp; } } void Application::displaySide(Camera& whichCamera) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // transform by eye offset // flip x if in mirror mode (also requires reversing winding order for backface culling) if (Menu::getInstance()->isOptionChecked(MenuOption::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::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::angle(rotation), axis.x, axis.y, axis.z); glTranslatef(-whichCamera.getPosition().x, -whichCamera.getPosition().y, -whichCamera.getPosition().z); // Setup 3D lights (after the camera transform, so that they are positioned in world space) setupWorldLight(whichCamera); if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... stars..."); if (!_stars.getFileLoaded()) { _stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0); } // 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()); 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); } // draw the sky dome if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... atmosphere..."); _environment.renderAtmospheres(whichCamera); } glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); // Enable to show line from me to the voxel I am touching //renderLineToTouchedVoxel(); //renderThrustAtVoxel(_voxelThrust); // draw a red sphere float sphereRadius = 0.25f; glColor3f(1,0,0); glPushMatrix(); glutSolidSphere(sphereRadius, 15, 15); glPopMatrix(); // disable specular lighting for ground and voxels glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR); //draw a grid ground plane.... if (Menu::getInstance()->isOptionChecked(MenuOption::GroundPlane)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... ground plane..."); // draw grass plane with fog glEnable(GL_FOG); glEnable(GL_NORMALIZE); const float FOG_COLOR[] = { 1.0f, 1.0f, 1.0f, 1.0f }; glFogfv(GL_FOG_COLOR, FOG_COLOR); glFogi(GL_FOG_MODE, GL_EXP2); glFogf(GL_FOG_DENSITY, 0.025f); glPushMatrix(); const float GRASS_PLANE_SIZE = 256.0f; glTranslatef(-GRASS_PLANE_SIZE * 0.5f, -0.01f, GRASS_PLANE_SIZE * 0.5f); glScalef(GRASS_PLANE_SIZE, 1.0f, GRASS_PLANE_SIZE); glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); glColor3ub(70, 134, 74); const int GRASS_DIVISIONS = 40; _geometryCache.renderSquare(GRASS_DIVISIONS, GRASS_DIVISIONS); glPopMatrix(); glDisable(GL_FOG); glDisable(GL_NORMALIZE); //renderGroundPlaneGrid(EDGE_SIZE_GROUND_PLANE, _audio.getCollisionSoundMagnitude()); } // Draw Cloud Particles _cloud.render(); // Draw voxels if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxels..."); if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) { _voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures)); } } // restore default, white specular glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR); // indicate what we'll be adding/removing in mouse mode, if anything if (_mouseVoxel.s != 0) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxels TOOLS UX..."); glDisable(GL_LIGHTING); glPushMatrix(); glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE); if (_nudgeStarted) { renderNudgeGuide(_nudgeGuidePosition.x, _nudgeGuidePosition.y, _nudgeGuidePosition.z, _nudgeVoxel.s); renderNudgeGrid(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s, _mouseVoxel.s); glPushMatrix(); glTranslatef(_nudgeVoxel.x + _nudgeVoxel.s * 0.5f, _nudgeVoxel.y + _nudgeVoxel.s * 0.5f, _nudgeVoxel.z + _nudgeVoxel.s * 0.5f); glColor3ub(255, 255, 255); glLineWidth(4.0f); glutWireCube(_nudgeVoxel.s); glPopMatrix(); } else { renderMouseVoxelGrid(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); } if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode)) { // use a contrasting color so that we can see what we're doing glColor3ub(_mouseVoxel.red + 128, _mouseVoxel.green + 128, _mouseVoxel.blue + 128); } else { glColor3ub(_mouseVoxel.red, _mouseVoxel.green, _mouseVoxel.blue); } if (_nudgeStarted) { // render nudge guide cube glTranslatef(_nudgeGuidePosition.x + _nudgeVoxel.s*0.5f, _nudgeGuidePosition.y + _nudgeVoxel.s*0.5f, _nudgeGuidePosition.z + _nudgeVoxel.s*0.5f); glLineWidth(4.0f); glutWireCube(_nudgeVoxel.s); } else { glTranslatef(_mouseVoxel.x + _mouseVoxel.s*0.5f, _mouseVoxel.y + _mouseVoxel.s*0.5f, _mouseVoxel.z + _mouseVoxel.s*0.5f); glLineWidth(4.0f); glutWireCube(_mouseVoxel.s); } glLineWidth(1.0f); glPopMatrix(); glEnable(GL_LIGHTING); } if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && _pasteMode) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... PASTE Preview..."); glPushMatrix(); glTranslatef(_mouseVoxel.x * TREE_SCALE, _mouseVoxel.y * TREE_SCALE, _mouseVoxel.z * TREE_SCALE); glScalef(_mouseVoxel.s, _mouseVoxel.s, _mouseVoxel.s); _sharedVoxelSystem.render(true); glPopMatrix(); } _myAvatar.renderScreenTint(SCREEN_TINT_BEFORE_AVATARS, whichCamera); if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... Avatars..."); // Render avatars of other nodes NodeList* nodeList = NodeList::getInstance(); for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { node->lock(); if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { Avatar *avatar = (Avatar *)node->getLinkedData(); if (!avatar->isInitialized()) { avatar->init(); } // Set lookAt to myCamera on client side if other avatars are looking at client if (isLookingAtMyAvatar(avatar)) { avatar->getHead().setLookAtPosition(_myCamera.getPosition()); } avatar->render(false, Menu::getInstance()->isOptionChecked(MenuOption::AvatarAsBalls)); avatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors)); } node->unlock(); } // Render my own Avatar if (_myCamera.getMode() == CAMERA_MODE_MIRROR && !_faceshift.isActive()) { _myAvatar.getHead().setLookAtPosition(_myCamera.getPosition()); } _myAvatar.render(Menu::getInstance()->isOptionChecked(MenuOption::Mirror), Menu::getInstance()->isOptionChecked(MenuOption::AvatarAsBalls)); _myAvatar.setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors)); if (Menu::getInstance()->isOptionChecked(MenuOption::LookAtIndicator) && _lookatTargetAvatar) { renderLookatIndicator(_lookatOtherPosition, whichCamera); } } _myAvatar.renderScreenTint(SCREEN_TINT_AFTER_AVATARS, whichCamera); // Render the world box if (!Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { renderWorldBox(); } // render the ambient occlusion effect if enabled if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... AmbientOcclusion..."); _ambientOcclusionEffect.render(); } // brad's frustum for debugging if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... renderViewFrustum..."); renderViewFrustum(_viewFrustum); } // render voxel fades if they exist if (_voxelFades.size() > 0) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxel fades..."); for(std::vector::iterator fade = _voxelFades.begin(); fade != _voxelFades.end();) { fade->render(); if(fade->isDone()) { fade = _voxelFades.erase(fade); } else { ++fade; } } } { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... renderFollowIndicator..."); renderFollowIndicator(); } // render transmitter pick ray, if non-empty if (_transmitterPickStart != _transmitterPickEnd) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... transmitter pick ray..."); Glower glower; const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f }; glColor3fv(TRANSMITTER_PICK_COLOR); glLineWidth(3.0f); glBegin(GL_LINES); glVertex3f(_transmitterPickStart.x, _transmitterPickStart.y, _transmitterPickStart.z); glVertex3f(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z); glEnd(); glLineWidth(1.0f); glPushMatrix(); glTranslatef(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z); const float PICK_END_RADIUS = 0.025f; glutSolidSphere(PICK_END_RADIUS, 8, 8); glPopMatrix(); } } void Application::displayOverlay() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displayOverlay()"); // Render 2D overlay: I/O level bar graphs and text glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, _glWidget->width(), _glWidget->height(), 0); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); // Display a single screen-size quad to create an alpha blended 'collision' flash float collisionSoundMagnitude = _audio.getCollisionSoundMagnitude(); const float VISIBLE_COLLISION_SOUND_MAGNITUDE = 0.5f; if (collisionSoundMagnitude > VISIBLE_COLLISION_SOUND_MAGNITUDE) { renderCollisionOverlay(_glWidget->width(), _glWidget->height(), _audio.getCollisionSoundMagnitude()); } #ifndef _WIN32 _audio.render(_glWidget->width(), _glWidget->height()); if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) { _audioScope.render(20, _glWidget->height() - 200); } #endif //noiseTest(_glWidget->width(), _glWidget->height()); if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse) && !Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && USING_INVENSENSE_MPU9150) { // Display small target box at center or head mouse target that can also be used to measure LOD glColor3f(1.0, 1.0, 1.0); glDisable(GL_LINE_SMOOTH); const int PIXEL_BOX = 16; glBegin(GL_LINES); glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY); glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY); glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2); glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2); glEnd(); glEnable(GL_LINE_SMOOTH); glColor3f(1.f, 0.f, 0.f); glPointSize(3.0f); glDisable(GL_POINT_SMOOTH); glBegin(GL_POINTS); glVertex2f(_headMouseX - 1, _headMouseY + 1); glEnd(); } // Show detected levels from the serial I/O ADC channel sensors if (_displayLevels) _serialHeadSensor.renderLevels(_glWidget->width(), _glWidget->height()); // Show hand transmitter data if detected if (_myTransmitter.isConnected()) { _myTransmitter.renderLevels(_glWidget->width(), _glWidget->height()); } // Display stats and log text onscreen glLineWidth(1.0f); glPointSize(1.0f); if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { displayStats(); } // testing rendering coverage map if (Menu::getInstance()->isOptionChecked(MenuOption::CoverageMapV2)) { renderCoverageMapV2(); } if (Menu::getInstance()->isOptionChecked(MenuOption::CoverageMap)) { renderCoverageMap(); } if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) { _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); } if (Menu::getInstance()->isOptionChecked(MenuOption::Log)) { LogDisplay::instance.render(_glWidget->width(), _glWidget->height()); } // Show chat entry field if (_chatEntryOn) { _chatEntry.render(_glWidget->width(), _glWidget->height()); } // Show on-screen msec timer if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) { char frameTimer[10]; uint64_t mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); drawtext(_glWidget->width() - 100, _glWidget->height() - 20, 0.30, 0, 1.0, 0, frameTimer, 0, 0, 0); drawtext(_glWidget->width() - 102, _glWidget->height() - 22, 0.30, 0, 1.0, 0, frameTimer, 1, 1, 1); } // Stats at upper right of screen about who domain server is telling us about glPointSize(1.0f); char nodes[100]; NodeList* nodeList = NodeList::getInstance(); int totalAvatars = 0, totalServers = 0; for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { node->getType() == NODE_TYPE_AGENT ? totalAvatars++ : totalServers++; } sprintf(nodes, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars); drawtext(_glWidget->width() - 150, 20, 0.10, 0, 1.0, 0, nodes, 1, 0, 0); // render the webcam input frame _webcam.renderPreview(_glWidget->width(), _glWidget->height()); _palette.render(_glWidget->width(), _glWidget->height()); QAction* paintColorAction = NULL; if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelGetColorMode) && (paintColorAction = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor))->data().value() != _swatch.getColor()) { QColor color = paintColorAction->data().value(); TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50); const char line1[] = "Assign this color to a swatch"; const char line2[] = "by choosing a key from 1 to 8."; int left = (_glWidget->width() - POPUP_WIDTH - 2 * POPUP_MARGIN) / 2; int top = _glWidget->height() / 40; glBegin(GL_POLYGON); glColor3f(0.0f, 0.0f, 0.0f); for (double a = M_PI; a < 1.5f * M_PI; a += POPUP_STEP) { glVertex2f(left + POPUP_MARGIN * cos(a) , top + POPUP_MARGIN * sin(a)); } for (double a = 1.5f * M_PI; a < 2.0f * M_PI; a += POPUP_STEP) { glVertex2f(left + POPUP_WIDTH + POPUP_MARGIN * cos(a), top + POPUP_MARGIN * sin(a)); } for (double a = 0.0f; a < 0.5f * M_PI; a += POPUP_STEP) { glVertex2f(left + POPUP_WIDTH + POPUP_MARGIN * cos(a), top + POPUP_HEIGHT + POPUP_MARGIN * sin(a)); } for (double a = 0.5f * M_PI; a < 1.0f * M_PI; a += POPUP_STEP) { glVertex2f(left + POPUP_MARGIN * cos(a) , top + POPUP_HEIGHT + POPUP_MARGIN * sin(a)); } glEnd(); glBegin(GL_QUADS); glColor3f(color.redF(), color.greenF(), color.blueF()); glVertex2f(left , top); glVertex2f(left + SWATCH_WIDTH, top); glVertex2f(left + SWATCH_WIDTH, top + SWATCH_HEIGHT); glVertex2f(left , top + SWATCH_HEIGHT); glEnd(); glColor3f(1.0f, 1.0f, 1.0f); textRenderer.draw(left + SWATCH_WIDTH + POPUP_MARGIN, top + FIRST_LINE_OFFSET , line1); textRenderer.draw(left + SWATCH_WIDTH + POPUP_MARGIN, top + SECOND_LINE_OFFSET, line2); } else { _swatch.checkColor(); } if (_pieMenu.isDisplayed()) { _pieMenu.render(); } glPopMatrix(); } void Application::displayStats() { int statsVerticalOffset = 8; char stats[200]; sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps ", _fps, _packetsPerSecond, (float)_bytesPerSecond * 8.f / 1000000.f); drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, stats); if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0; NodeList* nodeList = NodeList::getInstance(); Node* audioMixerNode = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER); Node* avatarMixerNode = nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0; pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0; // Now handle voxel servers, since there could be more than one, we average their ping times unsigned long totalPingVoxel = 0; int voxelServerCount = 0; for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getType() == NODE_TYPE_VOXEL_SERVER) { totalPingVoxel += node->getPingMs(); voxelServerCount++; if (pingVoxelMax < node->getPingMs()) { pingVoxelMax = node->getPingMs(); } } } if (voxelServerCount) { pingVoxel = totalPingVoxel/voxelServerCount; } char pingStats[200]; sprintf(pingStats, "Ping audio/avatar/voxel: %d / %d / %d avg %d max ", pingAudio, pingAvatar, pingVoxel, pingVoxelMax); drawtext(10, statsVerticalOffset + 35, 0.10f, 0, 1.0, 0, pingStats); } char avatarStats[200]; glm::vec3 avatarPos = _myAvatar.getPosition(); sprintf(avatarStats, "Avatar: pos %.3f, %.3f, %.3f, vel %.1f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, glm::length(_myAvatar.getVelocity()), _myAvatar.getBodyYaw()); drawtext(10, statsVerticalOffset + 55, 0.10f, 0, 1.0, 0, avatarStats); std::stringstream voxelStats; voxelStats.precision(4); voxelStats << "Voxels Rendered: " << _voxels.getVoxelsRendered() / 1000.f << "K " << "Updated: " << _voxels.getVoxelsUpdated()/1000.f << "K " << "Max: " << _voxels.getMaxVoxels()/1000.f << "K "; drawtext(10, statsVerticalOffset + 230, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); voxelStats << "Voxels Memory RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB " << "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB "; if (_voxels.hasVoxelMemoryUsageGPU()) { voxelStats << "GPU: " << _voxels.getVoxelMemoryUsageGPU() / 1000000.f << "MB "; } drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); char* voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_VOXELS); voxelStats << "Voxels Sent from Server: " << voxelDetails; drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ELAPSED); voxelStats << "Scene Send Time from Server: " << voxelDetails; drawtext(10, statsVerticalOffset + 290, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ENCODE); voxelStats << "Encode Time on Server: " << voxelDetails; drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_MODE); voxelStats << "Sending Mode: " << voxelDetails; drawtext(10, statsVerticalOffset + 330, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); Node *avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); char avatarMixerStats[200]; if (avatarMixer) { sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", roundf(avatarMixer->getAverageKilobitsPerSecond()), roundf(avatarMixer->getAveragePacketsPerSecond())); } else { sprintf(avatarMixerStats, "No Avatar Mixer"); } drawtext(10, statsVerticalOffset + 350, 0.10f, 0, 1.0, 0, avatarMixerStats); drawtext(10, statsVerticalOffset + 450, 0.10f, 0, 1.0, 0, (char *)LeapManager::statusString().c_str()); if (_perfStatsOn) { // Get the PerfStats group details. We need to allocate and array of char* long enough to hold 1+groups char** perfStatLinesArray = new char*[PerfStat::getGroupCount()+1]; int lines = PerfStat::DumpStats(perfStatLinesArray); int atZ = 150; // arbitrary place on screen that looks good for (int line=0; line < lines; line++) { drawtext(10, statsVerticalOffset + atZ, 0.10f, 0, 1.0, 0, perfStatLinesArray[line]); delete perfStatLinesArray[line]; // we're responsible for cleanup perfStatLinesArray[line]=NULL; atZ+=20; // height of a line } delete []perfStatLinesArray; // we're responsible for cleanup } } void Application::renderThrustAtVoxel(const glm::vec3& thrust) { if (_mousePressed) { glColor3f(1, 0, 0); glLineWidth(2.0f); glBegin(GL_LINES); glm::vec3 voxelTouched = getMouseVoxelWorldCoordinates(_mouseVoxelDragging); glVertex3f(voxelTouched.x, voxelTouched.y, voxelTouched.z); glVertex3f(voxelTouched.x + thrust.x, voxelTouched.y + thrust.y, voxelTouched.z + thrust.z); glEnd(); } } void Application::renderLineToTouchedVoxel() { // Draw a teal line to the voxel I am currently dragging on if (_mousePressed) { glColor3f(0, 1, 1); glLineWidth(2.0f); glBegin(GL_LINES); glm::vec3 voxelTouched = getMouseVoxelWorldCoordinates(_mouseVoxelDragging); glVertex3f(voxelTouched.x, voxelTouched.y, voxelTouched.z); glm::vec3 headPosition = _myAvatar.getHeadJointPosition(); glVertex3fv(&headPosition.x); glEnd(); } } glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) { float horizontalScale = _glWidget->width() / 2.0f; float verticalScale = _glWidget->height() / 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->height()); return screenPoint; } // render the coverage map on screen void Application::renderCoverageMapV2() { //qDebug("renderCoverageMap()\n"); glDisable(GL_LIGHTING); glLineWidth(2.0); glBegin(GL_LINES); glColor3f(0,1,1); renderCoverageMapsV2Recursively(&_voxels.myCoverageMapV2); glEnd(); glEnable(GL_LIGHTING); } void Application::renderCoverageMapsV2Recursively(CoverageMapV2* map) { // render ourselves... if (map->isCovered()) { BoundingBox box = map->getBoundingBox(); glm::vec2 firstPoint = getScaledScreenPoint(box.getVertex(0)); glm::vec2 lastPoint(firstPoint); for (int i = 1; i < box.getVertexCount(); i++) { glm::vec2 thisPoint = getScaledScreenPoint(box.getVertex(i)); glVertex2f(lastPoint.x, lastPoint.y); glVertex2f(thisPoint.x, thisPoint.y); lastPoint = thisPoint; } glVertex2f(lastPoint.x, lastPoint.y); glVertex2f(firstPoint.x, firstPoint.y); } else { // iterate our children and call render on them. for (int i = 0; i < CoverageMapV2::NUMBER_OF_CHILDREN; i++) { CoverageMapV2* childMap = map->getChild(i); if (childMap) { renderCoverageMapsV2Recursively(childMap); } } } } // render the coverage map on screen void Application::renderCoverageMap() { //qDebug("renderCoverageMap()\n"); glDisable(GL_LIGHTING); glLineWidth(2.0); glBegin(GL_LINES); glColor3f(0,0,1); renderCoverageMapsRecursively(&_voxels.myCoverageMap); glEnd(); glEnable(GL_LIGHTING); } void Application::renderCoverageMapsRecursively(CoverageMap* map) { for (int i = 0; i < map->getPolygonCount(); i++) { VoxelProjectedPolygon* polygon = map->getPolygon(i); if (polygon->getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) { glColor3f(.5,0,0); // dark red } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT)) { glColor3f(.5,.5,0); // dark yellow } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT)) { glColor3f(.5,.5,.5); // gray } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT | PROJECTION_BOTTOM)) { glColor3f(.5,0,.5); // dark magenta } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM)) { glColor3f(.75,0,0); // red } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP)) { glColor3f(1,0,1); // magenta } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT | PROJECTION_TOP)) { glColor3f(0,0,1); // Blue } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT | PROJECTION_TOP)) { glColor3f(0,1,0); // green } else if (polygon->getProjectionType() == (PROJECTION_NEAR)) { glColor3f(1,1,0); // yellow } else if (polygon->getProjectionType() == (PROJECTION_FAR | PROJECTION_RIGHT | PROJECTION_BOTTOM)) { glColor3f(0,.5,.5); // dark cyan } else { glColor3f(1,0,0); } glm::vec2 firstPoint = getScaledScreenPoint(polygon->getVertex(0)); glm::vec2 lastPoint(firstPoint); for (int i = 1; i < polygon->getVertexCount(); i++) { glm::vec2 thisPoint = getScaledScreenPoint(polygon->getVertex(i)); glVertex2f(lastPoint.x, lastPoint.y); glVertex2f(thisPoint.x, thisPoint.y); lastPoint = thisPoint; } glVertex2f(lastPoint.x, lastPoint.y); glVertex2f(firstPoint.x, firstPoint.y); } // iterate our children and call render on them. for (int i = 0; i < CoverageMapV2::NUMBER_OF_CHILDREN; i++) { CoverageMap* childMap = map->getChild(i); if (childMap) { renderCoverageMapsRecursively(childMap); } } } ///////////////////////////////////////////////////////////////////////////////////// // 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); // GREEN!!! 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::injectVoxelAddedSoundEffect() { AudioInjector* voxelInjector = AudioInjectionManager::injectorWithCapacity(11025); if (voxelInjector) { voxelInjector->setPosition(glm::vec3(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z)); //voxelInjector->setBearing(-1 * _myAvatar.getAbsoluteHeadYaw()); voxelInjector->setVolume (16 * pow (_mouseVoxel.s, 2) / .0000001); //255 is max, and also default value /* for (int i = 0; i < 22050; i++) { if (i % 4 == 0) { voxelInjector->addSample(4000); } else if (i % 4 == 1) { voxelInjector->addSample(0); } else if (i % 4 == 2) { voxelInjector->addSample(-4000); } else { voxelInjector->addSample(0); } */ const float BIG_VOXEL_MIN_SIZE = .01f; for (int i = 0; i < 11025; i++) { /* A440 square wave if (sin(i * 2 * PIE / 50)>=0) { voxelInjector->addSample(4000); } else { voxelInjector->addSample(-4000); } */ if (_mouseVoxel.s > BIG_VOXEL_MIN_SIZE) { voxelInjector->addSample(20000 * sin((i * 2 * PIE) / (500 * sin((i + 1) / 200)))); } else { voxelInjector->addSample(16000 * sin(i / (1.5 * log (_mouseVoxel.s / .0001) * ((i + 11025) / 5512.5)))); //808 } } //voxelInjector->addSample(32500 * sin(i/(2 * 1 * ((i+5000)/5512.5)))); //80 //voxelInjector->addSample(20000 * sin(i/(6 * (_mouseVoxel.s/.001) *((i+5512.5)/5512.5)))); //808 //voxelInjector->addSample(20000 * sin(i/(6 * ((i+5512.5)/5512.5)))); //808 //voxelInjector->addSample(4000 * sin(i * 2 * PIE /50)); //A440 sine wave //voxelInjector->addSample(4000 * sin(i * 2 * PIE /50) * sin (i/500)); //A440 sine wave with amplitude modulation //FM library //voxelInjector->addSample(20000 * sin((i * 2 * PIE) /(500*sin((i+1)/200)))); //FM 1 dubstep //voxelInjector->addSample(20000 * sin((i * 2 * PIE) /(300*sin((i+1)/5.0)))); //FM 2 flange sweep //voxelInjector->addSample(10000 * sin((i * 2 * PIE) /(500*sin((i+1)/500.0)))); //FM 3 resonant pulse AudioInjectionManager::threadInjector(voxelInjector); } } bool Application::maybeEditVoxelUnderCursor() { if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode) || Menu::getInstance()->isOptionChecked(MenuOption::VoxelColorMode)) { if (_mouseVoxel.s != 0) { PACKET_TYPE message = Menu::getInstance()->isOptionChecked(MenuOption::DestructiveAddVoxel) ? PACKET_TYPE_SET_VOXEL_DESTRUCTIVE : PACKET_TYPE_SET_VOXEL; _voxelEditSender.sendVoxelEditMessage(message, _mouseVoxel); // create the voxel locally so it appears immediately _voxels.createVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s, _mouseVoxel.red, _mouseVoxel.green, _mouseVoxel.blue, Menu::getInstance()->isOptionChecked(MenuOption::DestructiveAddVoxel)); // Implement voxel fade effect VoxelFade fade(VoxelFade::FADE_OUT, 1.0f, 1.0f, 1.0f); const float VOXEL_BOUNDS_ADJUST = 0.01f; float slightlyBigger = _mouseVoxel.s * VOXEL_BOUNDS_ADJUST; fade.voxelDetails.x = _mouseVoxel.x - slightlyBigger; fade.voxelDetails.y = _mouseVoxel.y - slightlyBigger; fade.voxelDetails.z = _mouseVoxel.z - slightlyBigger; fade.voxelDetails.s = _mouseVoxel.s + slightlyBigger + slightlyBigger; _voxelFades.push_back(fade); // inject a sound effect injectVoxelAddedSoundEffect(); // remember the position for drag detection _justEditedVoxel = true; } } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode)) { deleteVoxelUnderCursor(); VoxelFade fade(VoxelFade::FADE_OUT, 1.0f, 1.0f, 1.0f); const float VOXEL_BOUNDS_ADJUST = 0.01f; float slightlyBigger = _mouseVoxel.s * VOXEL_BOUNDS_ADJUST; fade.voxelDetails.x = _mouseVoxel.x - slightlyBigger; fade.voxelDetails.y = _mouseVoxel.y - slightlyBigger; fade.voxelDetails.z = _mouseVoxel.z - slightlyBigger; fade.voxelDetails.s = _mouseVoxel.s + slightlyBigger + slightlyBigger; _voxelFades.push_back(fade); } else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelGetColorMode)) { eyedropperVoxelUnderCursor(); } else { return false; } return true; } void Application::deleteVoxelUnderCursor() { if (_mouseVoxel.s != 0) { // sending delete to the server is sufficient, server will send new version so we see updates soon enough _voxelEditSender.sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, _mouseVoxel); // delete it locally to see the effect immediately (and in case no voxel server is present) _voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); AudioInjector* voxelInjector = AudioInjectionManager::injectorWithCapacity(5000); if (voxelInjector) { voxelInjector->setPosition(glm::vec3(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z)); //voxelInjector->setBearing(0); //straight down the z axis voxelInjector->setVolume (255); //255 is max, and also default value for (int i = 0; i < 5000; i++) { voxelInjector->addSample(10000 * sin((i * 2 * PIE) / (500 * sin((i + 1) / 500.0)))); //FM 3 resonant pulse //voxelInjector->addSample(20000 * sin((i) /((4 / _mouseVoxel.s) * sin((i)/(20 * _mouseVoxel.s / .001))))); //FM 2 comb filter } AudioInjectionManager::threadInjector(voxelInjector); } } // remember the position for drag detection _justEditedVoxel = true; } void Application::eyedropperVoxelUnderCursor() { VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode && selectedNode->isColored()) { QColor selectedColor(selectedNode->getColor()[RED_INDEX], selectedNode->getColor()[GREEN_INDEX], selectedNode->getColor()[BLUE_INDEX]); if (selectedColor.isValid()) { QAction* voxelPaintColorAction = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor); voxelPaintColorAction->setData(selectedColor); voxelPaintColorAction->setIcon(Swatch::createIcon(selectedColor)); } } } void Application::toggleFollowMode() { glm::vec3 mouseRayOrigin, mouseRayDirection; _viewFrustum.computePickRay(_pieMenu.getX() / (float)_glWidget->width(), _pieMenu.getY() / (float)_glWidget->height(), mouseRayOrigin, mouseRayDirection); glm::vec3 eyePositionIgnored; uint16_t nodeIDIgnored; Avatar* leadingAvatar = findLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, eyePositionIgnored, nodeIDIgnored); _myAvatar.follow(leadingAvatar); } void Application::resetSensors() { _headMouseX = _mouseX = _glWidget->width() / 2; _headMouseY = _mouseY = _glWidget->height() / 2; if (_serialHeadSensor.isActive()) { _serialHeadSensor.resetAverages(); } _webcam.reset(); _faceshift.reset(); QCursor::setPos(_headMouseX, _headMouseY); _myAvatar.reset(); _myTransmitter.resetLevels(); _myAvatar.setVelocity(glm::vec3(0,0,0)); _myAvatar.setThrust(glm::vec3(0,0,0)); } 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(child), enabled); } } } void Application::setMenuShortcutsEnabled(bool enabled) { setShortcutsEnabled(_window->menuBar(), enabled); } void Application::updateCursor() { _glWidget->setCursor(OculusManager::isConnected() && _window->windowState().testFlag(Qt::WindowFullScreen) ? Qt::BlankCursor : Qt::ArrowCursor); } void Application::attachNewHeadToNode(Node* newNode) { if (newNode->getLinkedData() == NULL) { newNode->setLinkedData(new Avatar(newNode)); } } void Application::domainChanged(QString domain) { qDebug("Application title set to: %s.\n", domain.toStdString().c_str()); _window->setWindowTitle(domain); } void Application::nodeAdded(Node* node) { } void Application::nodeKilled(Node* node) { if (node->getType() == NODE_TYPE_VOXEL_SERVER) { uint16_t nodeID = node->getNodeID(); // see if this is the first we've heard of this node... if (_voxelServerJurisdictions.find(nodeID) != _voxelServerJurisdictions.end()) { unsigned char* rootCode = _voxelServerJurisdictions[nodeID].getRootOctalCode(); VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); printf("voxel server going away...... v[%f, %f, %f, %f]\n", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); // Add the jurisditionDetails object to the list of "fade outs" VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE); fade.voxelDetails = rootDetails; const float slightly_smaller = 0.99; fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; _voxelFades.push_back(fade); } } else if (node->getLinkedData() == _lookatTargetAvatar) { _lookatTargetAvatar = NULL; } } int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress) { // parse the incoming stats data, and stick it into our averaging stats object for now... even though this // means mixing in stats from potentially multiple servers. int statsMessageLength = _voxelSceneStats.unpackFromMessage(messageData, messageLength); // But, also identify the sender, and keep track of the contained jurisdiction root for this server Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress); // quick fix for crash... why would voxelServer be NULL? if (voxelServer) { uint16_t nodeID = voxelServer->getNodeID(); VoxelPositionSize rootDetails; voxelDetailsForCode(_voxelSceneStats.getJurisdictionRoot(), rootDetails); // see if this is the first we've heard of this node... if (_voxelServerJurisdictions.find(nodeID) == _voxelServerJurisdictions.end()) { printf("stats from new voxel server... v[%f, %f, %f, %f]\n", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); // Add the jurisditionDetails object to the list of "fade outs" VoxelFade fade(VoxelFade::FADE_OUT, NODE_ADDED_RED, NODE_ADDED_GREEN, NODE_ADDED_BLUE); fade.voxelDetails = rootDetails; const float slightly_smaller = 0.99; fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; _voxelFades.push_back(fade); } // 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 VoxelSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the // details from the VoxelSceneStats to construct the JurisdictionMap JurisdictionMap jurisdictionMap; jurisdictionMap.copyContents(_voxelSceneStats.getJurisdictionRoot(), _voxelSceneStats.getJurisdictionEndNodes()); _voxelServerJurisdictions[nodeID] = jurisdictionMap; } return statsMessageLength; } // Receive packets from other nodes/servers and decide what to do with them! void* Application::networkReceive(void* args) { sockaddr senderAddress; ssize_t bytesReceived; Application* app = Application::getInstance(); while (!app->_stopNetworkReceiveThread) { if (NodeList::getInstance()->getNodeSocket()->receive(&senderAddress, app->_incomingPacket, &bytesReceived)) { app->_packetCount++; app->_bytesCount += bytesReceived; if (packetVersionMatch(app->_incomingPacket)) { // only process this packet if we have a match on the packet version switch (app->_incomingPacket[0]) { case PACKET_TYPE_TRANSMITTER_DATA_V2: // V2 = IOS transmitter app app->_myTransmitter.processIncomingData(app->_incomingPacket, bytesReceived); break; case PACKET_TYPE_MIXED_AUDIO: app->_audio.addReceivedAudioToBuffer(app->_incomingPacket, bytesReceived); break; case PACKET_TYPE_VOXEL_DATA: case PACKET_TYPE_VOXEL_DATA_MONOCHROME: case PACKET_TYPE_Z_COMMAND: case PACKET_TYPE_ERASE_VOXEL: case PACKET_TYPE_VOXEL_STATS: case PACKET_TYPE_ENVIRONMENT_DATA: { // add this packet to our list of voxel packets and process them on the voxel processing app->_voxelProcessor.queueReceivedPacket(senderAddress, app->_incomingPacket, bytesReceived); break; } case PACKET_TYPE_BULK_AVATAR_DATA: NodeList::getInstance()->processBulkNodeData(&senderAddress, app->_incomingPacket, bytesReceived); getInstance()->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived); break; case PACKET_TYPE_AVATAR_URLS: processAvatarURLsMessage(app->_incomingPacket, bytesReceived); break; case PACKET_TYPE_AVATAR_FACE_VIDEO: processAvatarFaceVideoMessage(app->_incomingPacket, bytesReceived); break; case PACKET_TYPE_DATA_SERVER_GET: case PACKET_TYPE_DATA_SERVER_PUT: case PACKET_TYPE_DATA_SERVER_SEND: case PACKET_TYPE_DATA_SERVER_CONFIRM: DataServerClient::processMessageFromDataServer(app->_incomingPacket, bytesReceived); break; default: NodeList::getInstance()->processNodeData(&senderAddress, app->_incomingPacket, bytesReceived); break; } } } else if (!app->_enableNetworkThread) { break; } } if (app->_enableNetworkThread) { pthread_exit(0); } return NULL; } void Application::packetSentNotification(ssize_t length) { _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length); }