mirror of
https://github.com/overte-org/overte.git
synced 2025-07-15 20:16:44 +02:00
493 lines
19 KiB
C++
493 lines
19 KiB
C++
//
|
|
// 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 "CompositorHelper.h"
|
|
|
|
#include <memory>
|
|
#include <math.h>
|
|
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
|
|
#include <QtCore/QTimer>
|
|
#include <QtCore/QThread>
|
|
#include <QtWidgets/QApplication>
|
|
#include <QtWidgets/QDesktopWidget>
|
|
#include <QtGui/QWindow>
|
|
#include <QQuickWindow>
|
|
|
|
#include <DebugDraw.h>
|
|
#include <shared/QtHelpers.h>
|
|
#include <ui/Menu.h>
|
|
#include <NumericalConstants.h>
|
|
#include <DependencyManager.h>
|
|
#include <plugins/PluginManager.h>
|
|
#include <CursorManager.h>
|
|
#include <gl/GLWidget.h>
|
|
|
|
// Used to animate the magnification windows
|
|
|
|
//static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
|
|
|
|
static const float reticleSize = TWO_PI / 100.0f;
|
|
|
|
//EntityItemID CompositorHelper::_noItemId;
|
|
static QString _tooltipId;
|
|
|
|
const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(3960, 1188); // ~10% more pixel density than old version, 72dx240d FOV
|
|
const QRect CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT = QRect(956, 0, 2048, 1188); // don't include entire width only center 2048
|
|
const float CompositorHelper::VIRTUAL_UI_ASPECT_RATIO = (float)VIRTUAL_SCREEN_SIZE.x / (float)VIRTUAL_SCREEN_SIZE.y;
|
|
const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI * 3.0f / 2.0f, PI * 3.0f / 2.0f / VIRTUAL_UI_ASPECT_RATIO);
|
|
const vec2 CompositorHelper::MOUSE_EXTENTS_ANGULAR_SIZE = vec2(PI * 2.0f, PI * 0.95f); // horizontal: full sphere, vertical: ~5deg from poles
|
|
const vec2 CompositorHelper::MOUSE_EXTENTS_PIXELS = vec2(VIRTUAL_SCREEN_SIZE) * (MOUSE_EXTENTS_ANGULAR_SIZE / VIRTUAL_UI_TARGET_FOV);
|
|
|
|
// 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)));
|
|
}
|
|
|
|
// FIXME move to GLMHelpers
|
|
//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;
|
|
}
|
|
}
|
|
|
|
CompositorHelper::CompositorHelper() :
|
|
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")),
|
|
_reticleInterface(new ReticleInterface(this))
|
|
{
|
|
// FIX in a separate PR addressing the current mouse over entity bug
|
|
//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);
|
|
|
|
// // check the format of this href string before we parse it
|
|
// QString hrefString = properties.getHref();
|
|
|
|
// auto cursor = Cursor::Manager::instance().getCursor();
|
|
// if (!hrefString.isEmpty()) {
|
|
// if (!hrefString.startsWith("hifi:")) {
|
|
// hrefString.prepend("hifi://");
|
|
// }
|
|
|
|
// // parse out a QUrl from the hrefString
|
|
// QUrl href = QUrl(hrefString);
|
|
|
|
// _hoverItemTitle = href.host();
|
|
// _hoverItemDescription = properties.getDescription();
|
|
// cursor->setIcon(Cursor::Icon::LINK);
|
|
// } else {
|
|
// _hoverItemTitle.clear();
|
|
// _hoverItemDescription.clear();
|
|
// cursor->setIcon(Cursor::Icon::DEFAULT);
|
|
// }
|
|
// }
|
|
//});
|
|
|
|
//connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
|
// if (_hoverItemId == entityItemID) {
|
|
// _hoverItemId = _noItemId;
|
|
|
|
// _hoverItemTitle.clear();
|
|
// _hoverItemDescription.clear();
|
|
|
|
// auto cursor = Cursor::Manager::instance().getCursor();
|
|
// cursor->setIcon(Cursor::Icon::DEFAULT);
|
|
// if (!_tooltipId.isEmpty()) {
|
|
// Tooltip::closeTip(_tooltipId);
|
|
// _tooltipId.clear();
|
|
// }
|
|
// }
|
|
//});
|
|
}
|
|
|
|
|
|
bool CompositorHelper::isHMD() const {
|
|
return _currentDisplayPlugin && _currentDisplayPlugin->isHmd();
|
|
}
|
|
|
|
QPointF CompositorHelper::getMouseEventPosition(QMouseEvent* event) {
|
|
if (isHMD()) {
|
|
QMutexLocker locker(&_reticleLock);
|
|
return QPointF(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
|
|
}
|
|
return event->localPos();
|
|
}
|
|
|
|
bool CompositorHelper::shouldCaptureMouse() const {
|
|
// if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu
|
|
return _allowMouseCapture && isHMD() && QApplication::activeWindow() && !ui::Menu::isSomeSubmenuShown();
|
|
}
|
|
|
|
void CompositorHelper::setAllowMouseCapture(bool capture) {
|
|
if (capture != _allowMouseCapture) {
|
|
_allowMouseCapture = capture;
|
|
emit allowMouseCaptureChanged();
|
|
}
|
|
_allowMouseCapture = capture;
|
|
}
|
|
|
|
void CompositorHelper::handleLeaveEvent() {
|
|
if (shouldCaptureMouse()) {
|
|
|
|
//QWidget* mainWidget = (QWidget*)qApp->getWindow();
|
|
static QWidget* mainWidget = nullptr;
|
|
if (mainWidget == nullptr && _renderingWidget != nullptr) {
|
|
mainWidget = _renderingWidget->parentWidget();
|
|
}
|
|
QRect mainWidgetFrame;
|
|
{
|
|
mainWidgetFrame = _renderingWidget->geometry();
|
|
auto topLeft = mainWidgetFrame.topLeft();
|
|
auto topLeftScreen = _renderingWidget->mapToGlobal(topLeft);
|
|
mainWidgetFrame.moveTopLeft(topLeftScreen);
|
|
}
|
|
QRect uncoveredRect = mainWidgetFrame;
|
|
foreach(QWidget* widget, QApplication::topLevelWidgets()) {
|
|
if (widget->isWindow() && widget->isVisible() && widget != mainWidget) {
|
|
QRect widgetFrame = widget->frameGeometry();
|
|
if (widgetFrame.intersects(uncoveredRect)) {
|
|
QRect intersection = uncoveredRect & widgetFrame;
|
|
if (intersection.top() > uncoveredRect.top()) {
|
|
uncoveredRect.setBottom(intersection.top() - 1);
|
|
} else if (intersection.bottom() < uncoveredRect.bottom()) {
|
|
uncoveredRect.setTop(intersection.bottom() + 1);
|
|
}
|
|
|
|
if (intersection.left() > uncoveredRect.left()) {
|
|
uncoveredRect.setRight(intersection.left() - 1);
|
|
} else if (intersection.right() < uncoveredRect.right()) {
|
|
uncoveredRect.setLeft(intersection.right() + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_ignoreMouseMove = true;
|
|
auto sendToPos = uncoveredRect.center();
|
|
QCursor::setPos(sendToPos);
|
|
_lastKnownRealMouse = sendToPos;
|
|
}
|
|
}
|
|
|
|
|
|
bool CompositorHelper::handleRealMouseMoveEvent(bool sendFakeEvent) {
|
|
// If the mouse move came from a capture mouse related move, we completely ignore it.
|
|
// Note: if not going to synthesize event - do not touch _ignoreMouseMove flag
|
|
if (_ignoreMouseMove && sendFakeEvent) {
|
|
_ignoreMouseMove = false;
|
|
return true; // swallow the event
|
|
}
|
|
|
|
// If we're in HMD mode
|
|
if (shouldCaptureMouse()) {
|
|
QMutexLocker locker(&_reticleLock);
|
|
auto newPosition = QCursor::pos();
|
|
auto changeInRealMouse = newPosition - _lastKnownRealMouse;
|
|
auto newReticlePosition = _reticlePositionInHMD + toGlm(changeInRealMouse);
|
|
setReticlePosition(newReticlePosition, sendFakeEvent);
|
|
|
|
// Note: if not going to synthesize event - do not touch _ignoreMouseMove flag
|
|
if (sendFakeEvent) {
|
|
_ignoreMouseMove = true;
|
|
}
|
|
|
|
QCursor::setPos(QPoint(_lastKnownRealMouse.x(), _lastKnownRealMouse.y())); // move cursor back to where it was
|
|
return true; // swallow the event
|
|
} else {
|
|
_lastKnownRealMouse = QCursor::pos();
|
|
}
|
|
return false; // let the caller know to process the event
|
|
}
|
|
|
|
glm::vec2 CompositorHelper::getReticlePosition() const {
|
|
if (isHMD()) {
|
|
QMutexLocker locker(&_reticleLock);
|
|
return _reticlePositionInHMD;
|
|
}
|
|
return toGlm(_renderingWidget->mapFromGlobal(QCursor::pos()));
|
|
}
|
|
|
|
bool CompositorHelper::getReticleOverDesktop() const {
|
|
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
|
|
// but... if we're outside of the overlay area, we also want to call ourselves
|
|
// as being over the desktop.
|
|
if (isHMD()) {
|
|
QMutexLocker locker(&_reticleLock);
|
|
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
|
|
static const glm::vec2 minOverlayPosition;
|
|
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
|
|
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
|
|
return true;
|
|
}
|
|
}
|
|
return _isOverDesktop;
|
|
}
|
|
|
|
glm::vec2 CompositorHelper::getReticleMaximumPosition() const {
|
|
glm::vec2 result;
|
|
if (isHMD()) {
|
|
result = VIRTUAL_SCREEN_SIZE;
|
|
} else {
|
|
QRect rec = QApplication::desktop()->screenGeometry();
|
|
result = glm::vec2(rec.right(), rec.bottom());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void CompositorHelper::sendFakeMouseEvent() {
|
|
if (qApp->thread() != QThread::currentThread()) {
|
|
BLOCKING_INVOKE_METHOD(this, "sendFakeMouseEvent");
|
|
return;
|
|
}
|
|
|
|
if (_renderingWidget) {
|
|
// in HMD mode we need to fake our mouse moves...
|
|
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
|
|
auto button = Qt::NoButton;
|
|
auto buttons = QApplication::mouseButtons();
|
|
auto modifiers = QApplication::keyboardModifiers();
|
|
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
|
|
_fakeMouseEvent = true;
|
|
qApp->sendEvent(_renderingWidget, &event);
|
|
_fakeMouseEvent = false;
|
|
}
|
|
}
|
|
|
|
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
|
|
if (isHMD()) {
|
|
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
|
|
// FIXME don't allow negative mouseExtra
|
|
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
|
|
glm::vec2 minMouse = vec2(0) - mouseExtra;
|
|
glm::vec2 maxMouse = maxOverlayPosition + mouseExtra;
|
|
|
|
{
|
|
QMutexLocker locker(&_reticleLock);
|
|
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
|
|
}
|
|
|
|
if (sendFakeEvent) {
|
|
sendFakeMouseEvent();
|
|
}
|
|
} else {
|
|
const QPoint point(position.x, position.y);
|
|
QCursor::setPos(_renderingWidget->mapToGlobal(point));
|
|
}
|
|
}
|
|
|
|
void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& origin, glm::vec3& direction) const {
|
|
auto surfacePointAt = sphereSurfaceFromOverlay(cursorPos); // in world space
|
|
origin = vec3(_currentCamera[3]);
|
|
direction = glm::normalize(surfacePointAt - origin);
|
|
}
|
|
|
|
glm::mat4 CompositorHelper::getUiTransform() const {
|
|
glm::mat4 modelMat;
|
|
_modelTransform.getMatrix(modelMat);
|
|
return _sensorToWorldMatrix * modelMat;
|
|
}
|
|
|
|
//Finds the collision point of a world space ray
|
|
bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const {
|
|
glm::mat4 uiToWorld = getUiTransform();
|
|
glm::mat4 worldToUi = glm::inverse(uiToWorld);
|
|
glm::vec3 localPosition = transformPoint(worldToUi, position);
|
|
glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction));
|
|
|
|
const float UI_RADIUS = 1.0f;
|
|
float instersectionDistance;
|
|
if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &instersectionDistance)) {
|
|
result = transformPoint(uiToWorld, localPosition + localDirection * instersectionDistance);
|
|
#ifdef WANT_DEBUG
|
|
DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
|
|
#endif
|
|
return true;
|
|
} else {
|
|
#ifdef WANT_DEBUG
|
|
DebugDraw::getInstance().drawRay(position, position + (direction * 1000.0f), glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
|
|
#endif
|
|
}
|
|
return false;
|
|
}
|
|
|
|
glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const {
|
|
glm::vec2 result = sphericalPos;
|
|
result.x *= -1.0f;
|
|
result /= _textureFov;
|
|
result.x /= _textureAspectRatio;
|
|
result += 0.5f;
|
|
result *= _currentDisplayPlugin->getRecommendedUiSize();
|
|
return result;
|
|
}
|
|
|
|
glm::vec2 CompositorHelper::overlayToSpherical(const glm::vec2& overlayPos) const {
|
|
glm::vec2 result = overlayPos;
|
|
result /= _currentDisplayPlugin->getRecommendedUiSize();
|
|
result -= 0.5f;
|
|
result *= _textureFov;
|
|
result.x *= _textureAspectRatio;
|
|
result.x *= -1.0f;
|
|
return result;
|
|
}
|
|
|
|
glm::vec2 CompositorHelper::overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const {
|
|
auto UITransform = getUiTransform();
|
|
auto relativePosition4 = glm::inverse(UITransform) * vec4(sphereSurfacePoint, 1);
|
|
auto direction = vec3(relativePosition4) / relativePosition4.w;
|
|
// FIXME use a GLMHelper cartesianToSpherical after fixing the rotation signs.
|
|
glm::vec2 polar = glm::vec2(glm::atan(direction.x, -direction.z), glm::asin(direction.y)) * -1.0f;
|
|
auto overlayPos = sphericalToOverlay(polar);
|
|
return overlayPos;
|
|
}
|
|
|
|
glm::vec3 CompositorHelper::sphereSurfaceFromOverlay(const glm::vec2& overlay) const {
|
|
auto spherical = overlayToSpherical(overlay);
|
|
// FIXME use a GLMHelper sphericalToCartesian after fixing the rotation signs.
|
|
auto sphereSurfacePoint = getPoint(spherical.x, spherical.y);
|
|
auto UITransform = getUiTransform();
|
|
auto position4 = UITransform * vec4(sphereSurfacePoint, 1);
|
|
return vec3(position4) / position4.w;
|
|
}
|
|
|
|
void CompositorHelper::updateTooltips() {
|
|
//if (_hoverItemId != _noItemId) {
|
|
// quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs;
|
|
// if (_hoverItemEnterUsecs != UINT64_MAX && !_hoverItemTitle.isEmpty() && hoverDuration > TOOLTIP_DELAY) {
|
|
// // TODO Enable and position the tooltip
|
|
// _hoverItemEnterUsecs = UINT64_MAX;
|
|
// _tooltipId = Tooltip::showTip(_hoverItemTitle, _hoverItemDescription);
|
|
// }
|
|
//}
|
|
}
|
|
|
|
// eyePose and headPosition are in sensor space.
|
|
// the resulting matrix should be in view space.
|
|
glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const {
|
|
glm::mat4 result;
|
|
if (isHMD()) {
|
|
vec2 spherical = overlayToSpherical(getReticlePosition());
|
|
vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space
|
|
vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space
|
|
vec3 d = sensorSurfacePoint - headPosition;
|
|
vec3 reticlePosition;
|
|
if (glm::length(d) >= EPSILON) {
|
|
d = glm::normalize(d);
|
|
} else {
|
|
d = glm::normalize(overlaySurfacePoint);
|
|
}
|
|
// Our sensor to world matrix always has uniform scale
|
|
float sensorSpaceReticleDepth = getReticleDepth() / extractScale(_sensorToWorldMatrix).x;
|
|
reticlePosition = headPosition + (d * sensorSpaceReticleDepth);
|
|
quat reticleOrientation = cancelOutRoll(glm::quat_cast(_currentDisplayPlugin->getHeadPose()));
|
|
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * sensorSpaceReticleDepth);
|
|
return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition);
|
|
} else {
|
|
static const float CURSOR_PIXEL_SIZE = 32.0f;
|
|
const auto canvasSize = vec2(toGlm(_renderingWidget->size()));;
|
|
vec2 mousePosition = toGlm(_renderingWidget->mapFromGlobal(QCursor::pos()));
|
|
mousePosition /= canvasSize;
|
|
mousePosition *= 2.0;
|
|
mousePosition -= 1.0;
|
|
mousePosition.y *= -1.0f;
|
|
|
|
vec2 mouseSize = CURSOR_PIXEL_SIZE * Cursor::Manager::instance().getScale() / canvasSize;
|
|
result = glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
glm::mat4 CompositorHelper::getPoint2DTransform(const glm::vec2& point, float sizeX, float sizeY) const {
|
|
glm::mat4 result;
|
|
const auto canvasSize = vec2(toGlm(_renderingWidget->size()));;
|
|
QPoint qPoint(point.x,point.y);
|
|
vec2 position = toGlm(_renderingWidget->mapFromGlobal(qPoint));
|
|
position /= canvasSize;
|
|
position *= 2.0;
|
|
position -= 1.0;
|
|
position.y *= -1.0f;
|
|
|
|
vec2 size = vec2(sizeX / canvasSize.x, sizeY / canvasSize.y);
|
|
result = glm::scale(glm::translate(glm::mat4(), vec3(position, 0.0f)), vec3(size, 1.0f));
|
|
return result;
|
|
}
|
|
|
|
|
|
QVariant ReticleInterface::getPosition() const {
|
|
return vec2toVariant(_compositor->getReticlePosition());
|
|
}
|
|
|
|
void ReticleInterface::setPosition(QVariant position) {
|
|
_compositor->setReticlePosition(vec2FromVariant(position));
|
|
}
|
|
|
|
float ReticleInterface::getScale() const {
|
|
auto& cursorManager = Cursor::Manager::instance();
|
|
return cursorManager.getScale();
|
|
}
|
|
|
|
void ReticleInterface::setScale(float scale) {
|
|
auto& cursorManager = Cursor::Manager::instance();
|
|
cursorManager.setScale(scale);
|
|
}
|