//// ApplicationCompositor.cpp// interface/src/ui/overlays//// Created by Benjamin Arnold on 5/27/14.// Copyright 2014 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "InterfaceConfig.h" #include "ApplicationCompositor.h" #include <glm/gtc/type_ptr.hpp> #include <avatar/AvatarManager.h>#include <gpu/GLBackend.h>#include <CursorManager.h>#include <Tooltip.h> #include "Application.h" // Used to animate the magnification windowsstatic const float MAG_SPEED = 0.08f; static const quint64 MSECS_TO_USECS = 1000ULL;static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; static const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f };static const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f };static const float reticleSize = TWO_PI / 100.0f; static const float CONNECTION_STATUS_BORDER_COLOR[] = { 1.0f, 0.0f, 0.0f };static const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; static const float CURSOR_PIXEL_SIZE = 32.0f;static const float MOUSE_PITCH_RANGE = 1.0f * PI;static const float MOUSE_YAW_RANGE = 0.5f * TWO_PI;static const glm::vec2 MOUSE_RANGE(MOUSE_YAW_RANGE, MOUSE_PITCH_RANGE); static gpu::BufferPointer _hemiVertices;static gpu::BufferPointer _hemiIndices;static int _hemiIndexCount{ 0 };EntityItemID ApplicationCompositor::_noItemId;static QString _tooltipId; // Return a point's cartesian coordinates on a sphere from pitch and yawglm::vec3 getPoint(float yaw, float pitch) { return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)), glm::sin(-pitch), glm::cos(-pitch) * (-glm::cos(yaw)));} //Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should//be multiplied by dir and added to origin to get the location of the collisionbool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result){ //Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection //Compute A, B and C coefficients float a = glm::dot(dir, dir); float b = 2 * glm::dot(dir, origin); float c = glm::dot(origin, origin) - (r * r); //Find discriminant float disc = b * b - 4 * a * c; // if discriminant is negative there are no real roots, so return // false as ray misses sphere if (disc < 0) { return false; } // compute q as described above float distSqrt = sqrtf(disc); float q; if (b < 0) { q = (-b - distSqrt) / 2.0f; } else { q = (-b + distSqrt) / 2.0f; } // compute t0 and t1 float t0 = q / a; float t1 = c / q; // make sure t0 is smaller than t1 if (t0 > t1) { // if t0 is bigger than t1 swap them around float temp = t0; t0 = t1; t1 = temp; } // if t1 is less than zero, the object is in the ray's negative direction // and consequently the ray misses the sphere if (t1 < 0) { return false; } // if t0 is less than zero, the intersection point is at t1 if (t0 < 0) { *result = t1; return true; } else { // else the intersection point is at t0 *result = t0; return true; }} ApplicationCompositor::ApplicationCompositor() { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); memset(_magSizeMult, 0, sizeof(_magSizeMult)); auto geometryCache = DependencyManager::get<GeometryCache>(); _reticleQuad = geometryCache->allocateID(); _magnifierQuad = geometryCache->allocateID(); _magnifierBorder = geometryCache->allocateID(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { if (_hoverItemId != entityItemID) { _hoverItemId = entityItemID; _hoverItemEnterUsecs = usecTimestampNow(); auto properties = entityScriptingInterface->getEntityProperties(_hoverItemId); _hoverItemHref = properties.getHref(); auto cursor = Cursor::Manager::instance().getCursor(); if (!_hoverItemHref.isEmpty()) { cursor->setIcon(Cursor::Icon::LINK); } else { cursor->setIcon(Cursor::Icon::DEFAULT); } } }); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { if (_hoverItemId == entityItemID) { _hoverItemId = _noItemId; _hoverItemHref.clear(); auto cursor = Cursor::Manager::instance().getCursor(); cursor->setIcon(Cursor::Icon::DEFAULT); if (!_tooltipId.isEmpty()) { qDebug() << "Closing tooltip " << _tooltipId; Tooltip::closeTip(_tooltipId); _tooltipId.clear(); } } });} ApplicationCompositor::~ApplicationCompositor() {} void ApplicationCompositor::bindCursorTexture(gpu::Batch& batch, uint8_t cursorIndex) { auto& cursorManager = Cursor::Manager::instance(); auto cursor = cursorManager.getCursor(cursorIndex); auto iconId = cursor->getIcon(); if (!_cursors.count(iconId)) { auto iconPath = cursorManager.getIconImage(cursor->getIcon()); _cursors[iconId] = DependencyManager::get<TextureCache>()-> getImageTexture(iconPath); } batch.setUniformTexture(0, _cursors[iconId]);} // Draws the FBO texture for the screenvoid ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { if (_alpha == 0.0f) { return; } GLuint texture = qApp->getApplicationOverlay().getOverlayTexture(); if (!texture) { return; } updateTooltips(); vec2 canvasSize = qApp->getCanvasSize(); //Handle fading and deactivation/activation of UI gpu::Batch batch; renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get<GeometryCache>(); geometryCache->useSimpleDrawPipeline(batch); batch.setModelTransform(Transform()); batch.setViewTransform(Transform()); batch.setProjectionTransform(mat4()); batch._glBindTexture(GL_TEXTURE_2D, texture); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); // Doesn't actually render renderPointers(batch); //draw the mouse pointer // Get the mouse coordinates and convert to NDC [-1, 1] vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); // Invert the Y axis mousePosition.y *= -1.0f; Transform model; model.setTranslation(vec3(mousePosition, 0)); vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; model.setScale(vec3(mouseSize, 1.0f)); batch.setModelTransform(model); bindCursorTexture(batch); vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; geometryCache->renderUnitQuad(batch, vec4(1)); renderArgs->_context->render(batch);} vec2 getPolarCoordinates(const PalmData& palm) { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); auto avatarOrientation = myAvatar->getOrientation(); auto eyePos = myAvatar->getDefaultEyePosition(); glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); // Direction of the tip relative to the eye glm::vec3 tipDirection = tip - eyePos; // orient into avatar space tipDirection = glm::inverse(avatarOrientation) * tipDirection; // Normalize for trig functions tipDirection = glm::normalize(tipDirection); // Convert to polar coordinates glm::vec2 polar(glm::atan(tipDirection.x, -tipDirection.z), glm::asin(tipDirection.y)); return polar;} // Draws the FBO texture for Oculus rift.void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int eye) { if (_alpha == 0.0f) { return; } GLuint texture = qApp->getApplicationOverlay().getOverlayTexture(); if (!texture) { return; } updateTooltips(); vec2 canvasSize = qApp->getCanvasSize(); _textureAspectRatio = aspect(canvasSize); renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get<GeometryCache>(); gpu::Batch batch; geometryCache->useSimpleDrawPipeline(batch); batch._glDisable(GL_DEPTH_TEST); batch._glDisable(GL_CULL_FACE); batch._glBindTexture(GL_TEXTURE_2D, texture); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); batch.setViewTransform(Transform()); batch.setProjectionTransform(qApp->getEyeProjection(eye)); mat4 eyePose = qApp->getEyePose(eye); glm::mat4 overlayXfm = glm::inverse(eyePose); #ifdef DEBUG_OVERLAY { batch.setModelTransform(glm::translate(mat4(), vec3(0, 0, -2))); geometryCache->renderUnitQuad(batch, glm::vec4(1)); }#else { batch.setModelTransform(overlayXfm); drawSphereSection(batch); }#endif // Doesn't actually render renderPointers(batch); vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); bindCursorTexture(batch); MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); //Controller Pointers for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { glm::vec2 polar = getPolarCoordinates(palm); // Convert to quaternion mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); mat4 reticleXfm = overlayXfm * pointerXfm; reticleXfm = glm::scale(reticleXfm, reticleScale); batch.setModelTransform(reticleXfm); // Render reticle at location geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); } } //Mouse Pointer if (_reticleActive[MOUSE]) { glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(), _reticlePosition[MOUSE].y())); mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); mat4 reticleXfm = overlayXfm * pointerXfm; reticleXfm = glm::scale(reticleXfm, reticleScale); batch.setModelTransform(reticleXfm); geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); } renderArgs->_context->render(batch);} void ApplicationCompositor::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const { cursorPos *= qApp->getCanvasSize(); const glm::vec2 projection = screenToSpherical(cursorPos); // The overlay space orientation of the mouse coordinates const glm::quat orientation(glm::vec3(-projection.y, projection.x, 0.0f)); // FIXME We now have the direction of the ray FROM THE DEFAULT HEAD POSE. // Now we need to account for the actual camera position relative to the overlay glm::vec3 overlaySpaceDirection = glm::normalize(orientation * IDENTITY_FRONT); // We need the RAW camera orientation and position, because this is what the overlay is // rendered relative to const glm::vec3 overlayPosition = qApp->getCamera()->getPosition(); const glm::quat overlayOrientation = qApp->getCamera()->getRotation(); // Intersection UI overlay space glm::vec3 worldSpaceDirection = overlayOrientation * overlaySpaceDirection; glm::vec3 worldSpaceIntersection = (glm::normalize(worldSpaceDirection) * _oculusUIRadius) + overlayPosition; glm::vec3 worldSpaceHeadPosition = (overlayOrientation * glm::vec3(qApp->getHeadPose()[3])) + overlayPosition; // Intersection in world space origin = worldSpaceHeadPosition; direction = glm::normalize(worldSpaceIntersection - worldSpaceHeadPosition);} //Caculate the click location using one of the sixense controllers. Scale is not appliedQPoint ApplicationCompositor::getPalmClickLocation(const PalmData *palm) const { QPoint rv; auto canvasSize = qApp->getCanvasSize(); if (qApp->isHMDMode()) { glm::vec2 polar = getPolarCoordinates(*palm); glm::vec2 point = sphericalToScreen(-polar); rv.rx() = point.x; rv.ry() = point.y; } else { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); glm::dmat4 projection; qApp->getProjectionMatrix(&projection); glm::quat invOrientation = glm::inverse(myAvatar->getOrientation()); glm::vec3 eyePos = myAvatar->getDefaultEyePosition(); glm::vec3 tip = myAvatar->getLaserPointerTipPosition(palm); glm::vec3 tipPos = invOrientation * (tip - eyePos); glm::vec4 clipSpacePos = glm::vec4(projection * glm::dvec4(tipPos, 1.0)); glm::vec3 ndcSpacePos; if (clipSpacePos.w != 0) { ndcSpacePos = glm::vec3(clipSpacePos) / clipSpacePos.w; } rv.setX(((ndcSpacePos.x + 1.0) / 2.0) * canvasSize.x); rv.setY((1.0 - ((ndcSpacePos.y + 1.0) / 2.0)) * canvasSize.y); } return rv;} //Finds the collision point of a world space raybool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); glm::quat inverseOrientation = glm::inverse(myAvatar->getOrientation()); glm::vec3 relativePosition = inverseOrientation * (position - myAvatar->getDefaultEyePosition()); glm::vec3 relativeDirection = glm::normalize(inverseOrientation * direction); float t; if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getScale(), &t)){ result = position + direction * t; return true; } return false;} //Renders optional pointersvoid ApplicationCompositor::renderPointers(gpu::Batch& batch) { if (qApp->isHMDMode() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) { //If we are in oculus, render reticle later QPoint position = QPoint(qApp->getTrueMouseX(), qApp->getTrueMouseY()); _reticlePosition[MOUSE] = position; _reticleActive[MOUSE] = true; _magActive[MOUSE] = _magnifier; _reticleActive[LEFT_CONTROLLER] = false; _reticleActive[RIGHT_CONTROLLER] = false; } else if (qApp->getLastMouseMoveWasSimulated() && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { //only render controller pointer if we aren't already rendering a mouse pointer _reticleActive[MOUSE] = false; _magActive[MOUSE] = false; renderControllerPointers(batch); }} void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); //Static variables used for storing controller state static quint64 pressedTime[NUMBER_OF_RETICLES] = { 0ULL, 0ULL, 0ULL }; static bool isPressed[NUMBER_OF_RETICLES] = { false, false, false }; static bool stateWhenPressed[NUMBER_OF_RETICLES] = { false, false, false }; const HandData* handData = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHandData(); for (unsigned int palmIndex = 2; palmIndex < 4; palmIndex++) { const int index = palmIndex - 1; const PalmData* palmData = NULL; if (palmIndex >= handData->getPalms().size()) { return; } if (handData->getPalms()[palmIndex].isActive()) { palmData = &handData->getPalms()[palmIndex]; } else { continue; } int controllerButtons = palmData->getControllerButtons(); //Check for if we should toggle or drag the magnification window if (controllerButtons & BUTTON_3) { if (isPressed[index] == false) { //We are now dragging the window isPressed[index] = true; //set the pressed time in us pressedTime[index] = usecTimestampNow(); stateWhenPressed[index] = _magActive[index]; } } else if (isPressed[index]) { isPressed[index] = false; //If the button was only pressed for < 250 ms //then disable it. const int MAX_BUTTON_PRESS_TIME = 250 * MSECS_TO_USECS; if (usecTimestampNow() < pressedTime[index] + MAX_BUTTON_PRESS_TIME) { _magActive[index] = !stateWhenPressed[index]; } } //if we have the oculus, we should make the cursor smaller since it will be //magnified if (qApp->isHMDMode()) { QPoint point = getPalmClickLocation(palmData); _reticlePosition[index] = point; //When button 2 is pressed we drag the mag window if (isPressed[index]) { _magActive[index] = true; } // If oculus is enabled, we draw the crosshairs later continue; } auto canvasSize = qApp->getCanvasSize(); int mouseX, mouseY; if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseLasers)) { QPoint res = getPalmClickLocation(palmData); mouseX = res.x(); mouseY = res.y(); } else { // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection(); // Get the angles, scaled between (-0.5,0.5) float xAngle = (atan2(direction.z, direction.x) + M_PI_2); float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled float cursorRange = canvasSize.x * SixenseManager::getInstance().getCursorPixelRangeMult(); mouseX = (canvasSize.x / 2.0f + cursorRange * xAngle); mouseY = (canvasSize.y / 2.0f + cursorRange * yAngle); } //If the cursor is out of the screen then don't render it if (mouseX < 0 || mouseX >= (int)canvasSize.x || mouseY < 0 || mouseY >= (int)canvasSize.y) { _reticleActive[index] = false; continue;Lines 1–500 of 761. Load more
// // ApplicationCompositor.cpp // interface/src/ui/overlays // // Created by Benjamin Arnold on 5/27/14. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "InterfaceConfig.h" #include "ApplicationCompositor.h" #include <glm/gtc/type_ptr.hpp> #include <avatar/AvatarManager.h> #include <gpu/GLBackend.h> #include <CursorManager.h> #include <Tooltip.h> #include "Application.h" // Used to animate the magnification windows static const float MAG_SPEED = 0.08f; static const quint64 MSECS_TO_USECS = 1000ULL; static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; static const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; static const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; static const float reticleSize = TWO_PI / 100.0f; static const float CONNECTION_STATUS_BORDER_COLOR[] = { 1.0f, 0.0f, 0.0f }; static const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; static const float CURSOR_PIXEL_SIZE = 32.0f; static const float MOUSE_PITCH_RANGE = 1.0f * PI; static const float MOUSE_YAW_RANGE = 0.5f * TWO_PI; static const glm::vec2 MOUSE_RANGE(MOUSE_YAW_RANGE, MOUSE_PITCH_RANGE); static gpu::BufferPointer _hemiVertices; static gpu::BufferPointer _hemiIndices; static int _hemiIndexCount{ 0 }; EntityItemID ApplicationCompositor::_noItemId; static QString _tooltipId; // Return a point's cartesian coordinates on a sphere from pitch and yaw glm::vec3 getPoint(float yaw, float pitch) { return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)), glm::sin(-pitch), glm::cos(-pitch) * (-glm::cos(yaw))); } //Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should //be multiplied by dir and added to origin to get the location of the collision bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result) { //Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection //Compute A, B and C coefficients float a = glm::dot(dir, dir); float b = 2 * glm::dot(dir, origin); float c = glm::dot(origin, origin) - (r * r); //Find discriminant float disc = b * b - 4 * a * c; // if discriminant is negative there are no real roots, so return // false as ray misses sphere if (disc < 0) { return false; } // compute q as described above float distSqrt = sqrtf(disc); float q; if (b < 0) { q = (-b - distSqrt) / 2.0f; } else { q = (-b + distSqrt) / 2.0f; } // compute t0 and t1 float t0 = q / a; float t1 = c / q; // make sure t0 is smaller than t1 if (t0 > t1) { // if t0 is bigger than t1 swap them around float temp = t0; t0 = t1; t1 = temp; } // if t1 is less than zero, the object is in the ray's negative direction // and consequently the ray misses the sphere if (t1 < 0) { return false; } // if t0 is less than zero, the intersection point is at t1 if (t0 < 0) { *result = t1; return true; } else { // else the intersection point is at t0 *result = t0; return true; } } ApplicationCompositor::ApplicationCompositor() { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); memset(_magSizeMult, 0, sizeof(_magSizeMult)); auto geometryCache = DependencyManager::get<GeometryCache>(); _reticleQuad = geometryCache->allocateID(); _magnifierQuad = geometryCache->allocateID(); _magnifierBorder = geometryCache->allocateID(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { if (_hoverItemId != entityItemID) { _hoverItemId = entityItemID; _hoverItemEnterUsecs = usecTimestampNow(); auto properties = entityScriptingInterface->getEntityProperties(_hoverItemId); _hoverItemHref = properties.getHref(); auto cursor = Cursor::Manager::instance().getCursor(); if (!_hoverItemHref.isEmpty()) { cursor->setIcon(Cursor::Icon::LINK); } else { cursor->setIcon(Cursor::Icon::DEFAULT); } } }); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { if (_hoverItemId == entityItemID) { _hoverItemId = _noItemId; _hoverItemHref.clear(); auto cursor = Cursor::Manager::instance().getCursor(); cursor->setIcon(Cursor::Icon::DEFAULT); if (!_tooltipId.isEmpty()) { qDebug() << "Closing tooltip " << _tooltipId; Tooltip::closeTip(_tooltipId); _tooltipId.clear(); } } }); } ApplicationCompositor::~ApplicationCompositor() { } void ApplicationCompositor::bindCursorTexture(gpu::Batch& batch, uint8_t cursorIndex) { auto& cursorManager = Cursor::Manager::instance(); auto cursor = cursorManager.getCursor(cursorIndex); auto iconId = cursor->getIcon(); if (!_cursors.count(iconId)) { auto iconPath = cursorManager.getIconImage(cursor->getIcon()); _cursors[iconId] = DependencyManager::get<TextureCache>()-> getImageTexture(iconPath); } batch.setUniformTexture(0, _cursors[iconId]); } // Draws the FBO texture for the screen void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { if (_alpha == 0.0f) { return; } GLuint texture = qApp->getApplicationOverlay().getOverlayTexture(); if (!texture) { return; } updateTooltips(); vec2 canvasSize = qApp->getCanvasSize(); //Handle fading and deactivation/activation of UI gpu::Batch batch; renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get<GeometryCache>(); geometryCache->useSimpleDrawPipeline(batch); batch.setModelTransform(Transform()); batch.setViewTransform(Transform()); batch.setProjectionTransform(mat4()); batch._glBindTexture(GL_TEXTURE_2D, texture); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); // Doesn't actually render renderPointers(batch); //draw the mouse pointer // Get the mouse coordinates and convert to NDC [-1, 1] vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); // Invert the Y axis mousePosition.y *= -1.0f; Transform model; model.setTranslation(vec3(mousePosition, 0)); vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; model.setScale(vec3(mouseSize, 1.0f)); batch.setModelTransform(model); bindCursorTexture(batch); vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; geometryCache->renderUnitQuad(batch, vec4(1)); renderArgs->_context->render(batch); } vec2 getPolarCoordinates(const PalmData& palm) { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); auto avatarOrientation = myAvatar->getOrientation(); auto eyePos = myAvatar->getDefaultEyePosition(); glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); // Direction of the tip relative to the eye glm::vec3 tipDirection = tip - eyePos; // orient into avatar space tipDirection = glm::inverse(avatarOrientation) * tipDirection; // Normalize for trig functions tipDirection = glm::normalize(tipDirection); // Convert to polar coordinates glm::vec2 polar(glm::atan(tipDirection.x, -tipDirection.z), glm::asin(tipDirection.y)); return polar; } // Draws the FBO texture for Oculus rift. void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int eye) { if (_alpha == 0.0f) { return; } GLuint texture = qApp->getApplicationOverlay().getOverlayTexture(); if (!texture) { return; } updateTooltips(); vec2 canvasSize = qApp->getCanvasSize(); _textureAspectRatio = aspect(canvasSize); renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get<GeometryCache>(); gpu::Batch batch; geometryCache->useSimpleDrawPipeline(batch); batch._glDisable(GL_DEPTH_TEST); batch._glDisable(GL_CULL_FACE); batch._glBindTexture(GL_TEXTURE_2D, texture); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); batch.setViewTransform(Transform()); batch.setProjectionTransform(qApp->getEyeProjection(eye)); mat4 eyePose = qApp->getEyePose(eye); glm::mat4 overlayXfm = glm::inverse(eyePose); #ifdef DEBUG_OVERLAY { batch.setModelTransform(glm::translate(mat4(), vec3(0, 0, -2))); geometryCache->renderUnitQuad(batch, glm::vec4(1)); } #else { batch.setModelTransform(overlayXfm); drawSphereSection(batch); } #endif // Doesn't actually render renderPointers(batch); vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); bindCursorTexture(batch); MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); //Controller Pointers for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { glm::vec2 polar = getPolarCoordinates(palm); // Convert to quaternion mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); mat4 reticleXfm = overlayXfm * pointerXfm; reticleXfm = glm::scale(reticleXfm, reticleScale); batch.setModelTransform(reticleXfm); // Render reticle at location geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); } } //Mouse Pointer if (_reticleActive[MOUSE]) { glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(), _reticlePosition[MOUSE].y())); mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); mat4 reticleXfm = overlayXfm * pointerXfm; reticleXfm = glm::scale(reticleXfm, reticleScale); batch.setModelTransform(reticleXfm); geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); } renderArgs->_context->render(batch); } void ApplicationCompositor::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const { cursorPos *= qApp->getCanvasSize(); const glm::vec2 projection = screenToSpherical(cursorPos); // The overlay space orientation of the mouse coordinates const glm::quat orientation(glm::vec3(-projection.y, projection.x, 0.0f)); // FIXME We now have the direction of the ray FROM THE DEFAULT HEAD POSE. // Now we need to account for the actual camera position relative to the overlay glm::vec3 overlaySpaceDirection = glm::normalize(orientation * IDENTITY_FRONT); // We need the RAW camera orientation and position, because this is what the overlay is // rendered relative to const glm::vec3 overlayPosition = qApp->getCamera()->getPosition(); const glm::quat overlayOrientation = qApp->getCamera()->getRotation(); // Intersection UI overlay space glm::vec3 worldSpaceDirection = overlayOrientation * overlaySpaceDirection; glm::vec3 worldSpaceIntersection = (glm::normalize(worldSpaceDirection) * _oculusUIRadius) + overlayPosition; glm::vec3 worldSpaceHeadPosition = (overlayOrientation * glm::vec3(qApp->getHeadPose()[3])) + overlayPosition; // Intersection in world space origin = worldSpaceHeadPosition; direction = glm::normalize(worldSpaceIntersection - worldSpaceHeadPosition); } //Caculate the click location using one of the sixense controllers. Scale is not applied QPoint ApplicationCompositor::getPalmClickLocation(const PalmData *palm) const { QPoint rv; auto canvasSize = qApp->getCanvasSize(); if (qApp->isHMDMode()) { glm::vec2 polar = getPolarCoordinates(*palm); glm::vec2 point = sphericalToScreen(-polar); rv.rx() = point.x; rv.ry() = point.y; } else { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); glm::dmat4 projection; qApp->getProjectionMatrix(&projection); glm::quat invOrientation = glm::inverse(myAvatar->getOrientation()); glm::vec3 eyePos = myAvatar->getDefaultEyePosition(); glm::vec3 tip = myAvatar->getLaserPointerTipPosition(palm); glm::vec3 tipPos = invOrientation * (tip - eyePos); glm::vec4 clipSpacePos = glm::vec4(projection * glm::dvec4(tipPos, 1.0)); glm::vec3 ndcSpacePos; if (clipSpacePos.w != 0) { ndcSpacePos = glm::vec3(clipSpacePos) / clipSpacePos.w; } rv.setX(((ndcSpacePos.x + 1.0) / 2.0) * canvasSize.x); rv.setY((1.0 - ((ndcSpacePos.y + 1.0) / 2.0)) * canvasSize.y); } return rv; } //Finds the collision point of a world space ray bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); glm::quat inverseOrientation = glm::inverse(myAvatar->getOrientation()); glm::vec3 relativePosition = inverseOrientation * (position - myAvatar->getDefaultEyePosition()); glm::vec3 relativeDirection = glm::normalize(inverseOrientation * direction); float t; if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getScale(), &t)){ result = position + direction * t; return true; } return false; } //Renders optional pointers void ApplicationCompositor::renderPointers(gpu::Batch& batch) { if (qApp->isHMDMode() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) { //If we are in oculus, render reticle later QPoint position = QPoint(qApp->getTrueMouseX(), qApp->getTrueMouseY()); _reticlePosition[MOUSE] = position; _reticleActive[MOUSE] = true; _magActive[MOUSE] = _magnifier; _reticleActive[LEFT_CONTROLLER] = false; _reticleActive[RIGHT_CONTROLLER] = false; } else if (qApp->getLastMouseMoveWasSimulated() && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { //only render controller pointer if we aren't already rendering a mouse pointer _reticleActive[MOUSE] = false; _magActive[MOUSE] = false; renderControllerPointers(batch); } } void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); //Static variables used for storing controller state static quint64 pressedTime[NUMBER_OF_RETICLES] = { 0ULL, 0ULL, 0ULL }; static bool isPressed[NUMBER_OF_RETICLES] = { false, false, false }; static bool stateWhenPressed[NUMBER_OF_RETICLES] = { false, false, false }; const HandData* handData = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHandData(); for (unsigned int palmIndex = 2; palmIndex < 4; palmIndex++) { const int index = palmIndex - 1; const PalmData* palmData = NULL; if (palmIndex >= handData->getPalms().size()) { return; } if (handData->getPalms()[palmIndex].isActive()) { palmData = &handData->getPalms()[palmIndex]; } else { continue; } int controllerButtons = palmData->getControllerButtons(); //Check for if we should toggle or drag the magnification window if (controllerButtons & BUTTON_3) { if (isPressed[index] == false) { //We are now dragging the window isPressed[index] = true; //set the pressed time in us pressedTime[index] = usecTimestampNow(); stateWhenPressed[index] = _magActive[index]; } } else if (isPressed[index]) { isPressed[index] = false; //If the button was only pressed for < 250 ms //then disable it. const int MAX_BUTTON_PRESS_TIME = 250 * MSECS_TO_USECS; if (usecTimestampNow() < pressedTime[index] + MAX_BUTTON_PRESS_TIME) { _magActive[index] = !stateWhenPressed[index]; } } //if we have the oculus, we should make the cursor smaller since it will be //magnified if (qApp->isHMDMode()) { QPoint point = getPalmClickLocation(palmData); _reticlePosition[index] = point; //When button 2 is pressed we drag the mag window if (isPressed[index]) { _magActive[index] = true; } // If oculus is enabled, we draw the crosshairs later continue; } auto canvasSize = qApp->getCanvasSize(); int mouseX, mouseY; if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseLasers)) { QPoint res = getPalmClickLocation(palmData); mouseX = res.x(); mouseY = res.y(); } else { // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection(); // Get the angles, scaled between (-0.5,0.5) float xAngle = (atan2(direction.z, direction.x) + M_PI_2); float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled float cursorRange = canvasSize.x * SixenseManager::getInstance().getCursorPixelRangeMult(); mouseX = (canvasSize.x / 2.0f + cursorRange * xAngle); mouseY = (canvasSize.y / 2.0f + cursorRange * yAngle); } //If the cursor is out of the screen then don't render it if (mouseX < 0 || mouseX >= (int)canvasSize.x || mouseY < 0 || mouseY >= (int)canvasSize.y) { _reticleActive[index] = false; continue;