mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 21:53:47 +02:00
Merge pull request #8335 from hyperlogic/feature/hand-controller-web-entity-integration
handControllerGrab and web entity input integration
This commit is contained in:
commit
21bc06f154
21 changed files with 1069 additions and 318 deletions
|
@ -1,10 +0,0 @@
|
||||||
import QtQuick 2.3
|
|
||||||
import QtQuick.Controls 1.2
|
|
||||||
import QtWebEngine 1.1
|
|
||||||
|
|
||||||
WebEngineView {
|
|
||||||
id: root
|
|
||||||
anchors.fill: parent
|
|
||||||
objectName: "webview"
|
|
||||||
url: "about:blank"
|
|
||||||
}
|
|
|
@ -1067,70 +1067,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
|
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
||||||
[this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) {
|
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
|
||||||
if (_keyboardFocusedItem != entityItemID) {
|
setKeyboardFocusEntity(entityItemID);
|
||||||
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
|
|
||||||
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
|
|
||||||
if (EntityTypes::Web == properties.getType() && !properties.getLocked() && properties.getVisible()) {
|
|
||||||
auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(entityItemID);
|
|
||||||
RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get());
|
|
||||||
if (webEntity) {
|
|
||||||
webEntity->setProxyWindow(_window->windowHandle());
|
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
|
||||||
_keyboardMouseDevice->pluginFocusOutEvent();
|
|
||||||
}
|
|
||||||
_keyboardFocusedItem = entityItemID;
|
|
||||||
_lastAcceptedKeyPress = usecTimestampNow();
|
|
||||||
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
|
|
||||||
_keyboardFocusHighlight = new Cube3DOverlay();
|
|
||||||
_keyboardFocusHighlight->setAlpha(1.0f);
|
|
||||||
_keyboardFocusHighlight->setBorderSize(1.0f);
|
|
||||||
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
|
|
||||||
_keyboardFocusHighlight->setIsSolid(false);
|
|
||||||
_keyboardFocusHighlight->setPulseMin(0.5);
|
|
||||||
_keyboardFocusHighlight->setPulseMax(1.0);
|
|
||||||
_keyboardFocusHighlight->setColorPulse(1.0);
|
|
||||||
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
|
|
||||||
_keyboardFocusHighlight->setDrawInFront(true);
|
|
||||||
}
|
|
||||||
_keyboardFocusHighlight->setRotation(webEntity->getRotation());
|
|
||||||
_keyboardFocusHighlight->setPosition(webEntity->getPosition());
|
|
||||||
_keyboardFocusHighlight->setDimensions(webEntity->getDimensions() * 1.05f);
|
|
||||||
_keyboardFocusHighlight->setVisible(true);
|
|
||||||
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_keyboardFocusedItem == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) {
|
|
||||||
_keyboardFocusHighlight->setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity,
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) {
|
||||||
[=](const EntityItemID& entityItemID) {
|
if (entityItemID == _keyboardFocusedItem.get()) {
|
||||||
if (entityItemID == _keyboardFocusedItem) {
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
|
|
||||||
if (_keyboardFocusHighlight) {
|
|
||||||
_keyboardFocusHighlight->setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the user clicks somewhere where there is NO entity at all, we will release focus
|
// If the user clicks somewhere where there is NO entity at all, we will release focus
|
||||||
connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity,
|
connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, [=]() {
|
||||||
[=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event) {
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
|
|
||||||
if (_keyboardFocusHighlight) {
|
|
||||||
_keyboardFocusHighlight->setVisible(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(this, &Application::aboutToQuit, [=]() {
|
connect(this, &Application::aboutToQuit, [=]() {
|
||||||
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
if (_keyboardFocusHighlight) {
|
|
||||||
_keyboardFocusHighlight->setVisible(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add periodic checks to send user activity data
|
// Add periodic checks to send user activity data
|
||||||
|
@ -1403,11 +1356,13 @@ void Application::cleanupBeforeQuit() {
|
||||||
// FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete:
|
// FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete:
|
||||||
_applicationStateDevice.reset();
|
_applicationStateDevice.reset();
|
||||||
|
|
||||||
if (_keyboardFocusHighlightID > 0) {
|
{
|
||||||
getOverlays().deleteOverlay(_keyboardFocusHighlightID);
|
if (_keyboardFocusHighlightID > 0) {
|
||||||
_keyboardFocusHighlightID = -1;
|
getOverlays().deleteOverlay(_keyboardFocusHighlightID);
|
||||||
|
_keyboardFocusHighlightID = -1;
|
||||||
|
}
|
||||||
|
_keyboardFocusHighlight = nullptr;
|
||||||
}
|
}
|
||||||
_keyboardFocusHighlight = nullptr;
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
@ -2122,16 +2077,16 @@ bool Application::event(QEvent* event) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_keyboardFocusedItem.isInvalidID()) {
|
{
|
||||||
switch (event->type()) {
|
if (!_keyboardFocusedItem.get().isInvalidID()) {
|
||||||
|
switch (event->type()) {
|
||||||
case QEvent::KeyPress:
|
case QEvent::KeyPress:
|
||||||
case QEvent::KeyRelease: {
|
case QEvent::KeyRelease: {
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(_keyboardFocusedItem);
|
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedItem.get());
|
||||||
RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get());
|
if (entity && entity->getEventHandler()) {
|
||||||
if (webEntity && webEntity->getEventHandler()) {
|
|
||||||
event->setAccepted(false);
|
event->setAccepted(false);
|
||||||
QCoreApplication::sendEvent(webEntity->getEventHandler(), event);
|
QCoreApplication::sendEvent(entity->getEventHandler(), event);
|
||||||
if (event->isAccepted()) {
|
if (event->isAccepted()) {
|
||||||
_lastAcceptedKeyPress = usecTimestampNow();
|
_lastAcceptedKeyPress = usecTimestampNow();
|
||||||
return true;
|
return true;
|
||||||
|
@ -2142,6 +2097,7 @@ bool Application::event(QEvent* event) {
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2691,7 +2647,10 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
||||||
event->screenPos(), button,
|
event->screenPos(), button,
|
||||||
buttons, event->modifiers());
|
buttons, event->modifiers());
|
||||||
|
|
||||||
getEntities()->mouseMoveEvent(&mappedEvent);
|
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
|
||||||
|
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y()))) {
|
||||||
|
getEntities()->mouseMoveEvent(&mappedEvent);
|
||||||
|
}
|
||||||
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
||||||
|
|
||||||
// if one of our scripts have asked to capture this event, then stop processing it
|
// if one of our scripts have asked to capture this event, then stop processing it
|
||||||
|
@ -2981,14 +2940,6 @@ void Application::idle(float nsecsElapsed) {
|
||||||
_simCounter.increment();
|
_simCounter.increment();
|
||||||
|
|
||||||
PerformanceTimer perfTimer("idle");
|
PerformanceTimer perfTimer("idle");
|
||||||
// Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds
|
|
||||||
if (!_keyboardFocusedItem.isInvalidID()) {
|
|
||||||
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
|
|
||||||
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
|
|
||||||
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
|
|
||||||
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
|
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
|
||||||
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
|
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
|
||||||
|
@ -3002,6 +2953,27 @@ void Application::idle(float nsecsElapsed) {
|
||||||
static const float BIGGEST_DELTA_TIME_SECS = 0.25f;
|
static const float BIGGEST_DELTA_TIME_SECS = 0.25f;
|
||||||
update(glm::clamp(secondsSinceLastUpdate, 0.0f, BIGGEST_DELTA_TIME_SECS));
|
update(glm::clamp(secondsSinceLastUpdate, 0.0f, BIGGEST_DELTA_TIME_SECS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds
|
||||||
|
{
|
||||||
|
if (!_keyboardFocusedItem.get().isInvalidID()) {
|
||||||
|
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
|
||||||
|
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
|
||||||
|
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
|
||||||
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
|
} else {
|
||||||
|
// update position of highlight overlay
|
||||||
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedItem.get());
|
||||||
|
if (entity && _keyboardFocusHighlight) {
|
||||||
|
_keyboardFocusHighlight->setRotation(entity->getRotation());
|
||||||
|
_keyboardFocusHighlight->setPosition(entity->getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
PerformanceTimer perfTimer("pluginIdle");
|
PerformanceTimer perfTimer("pluginIdle");
|
||||||
PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()");
|
PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()");
|
||||||
|
@ -3586,6 +3558,54 @@ void Application::rotationModeChanged() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUuid Application::getKeyboardFocusEntity() const {
|
||||||
|
return _keyboardFocusedItem.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::setKeyboardFocusEntity(QUuid id) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
setKeyboardFocusEntity(entityItemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
|
||||||
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
if (_keyboardFocusedItem.get() != entityItemID) {
|
||||||
|
_keyboardFocusedItem.set(UNKNOWN_ENTITY_ID);
|
||||||
|
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
|
||||||
|
if (!properties.getLocked() && properties.getVisible()) {
|
||||||
|
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
|
||||||
|
if (entity && entity->wantsKeyboardFocus()) {
|
||||||
|
entity->setProxyWindow(_window->windowHandle());
|
||||||
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
|
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||||
|
}
|
||||||
|
_keyboardFocusedItem.set(entityItemID);
|
||||||
|
_lastAcceptedKeyPress = usecTimestampNow();
|
||||||
|
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
|
||||||
|
_keyboardFocusHighlight = new Cube3DOverlay();
|
||||||
|
_keyboardFocusHighlight->setAlpha(1.0f);
|
||||||
|
_keyboardFocusHighlight->setBorderSize(1.0f);
|
||||||
|
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
|
||||||
|
_keyboardFocusHighlight->setIsSolid(false);
|
||||||
|
_keyboardFocusHighlight->setPulseMin(0.5);
|
||||||
|
_keyboardFocusHighlight->setPulseMax(1.0);
|
||||||
|
_keyboardFocusHighlight->setColorPulse(1.0);
|
||||||
|
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
|
||||||
|
_keyboardFocusHighlight->setDrawInFront(false);
|
||||||
|
}
|
||||||
|
_keyboardFocusHighlight->setRotation(entity->getRotation());
|
||||||
|
_keyboardFocusHighlight->setPosition(entity->getPosition());
|
||||||
|
_keyboardFocusHighlight->setDimensions(entity->getDimensions() * 1.05f);
|
||||||
|
_keyboardFocusHighlight->setVisible(true);
|
||||||
|
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_keyboardFocusedItem.get() == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) {
|
||||||
|
_keyboardFocusHighlight->setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::updateDialogs(float deltaTime) const {
|
void Application::updateDialogs(float deltaTime) const {
|
||||||
PerformanceTimer perfTimer("updateDialogs");
|
PerformanceTimer perfTimer("updateDialogs");
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
|
@ -5642,3 +5662,48 @@ bool Application::makeRenderingContextCurrent() {
|
||||||
bool Application::isForeground() const {
|
bool Application::isForeground() const {
|
||||||
return _isForeground && !_window->isMinimized();
|
return _isForeground && !_window->isMinimized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::sendMousePressOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->mousePressOnEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendMouseMoveOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->mouseMoveOnEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendMouseReleaseOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->mouseReleaseOnEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendClickDownOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->clickDownOnEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendHoldingClickOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->holdingClickOnEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendClickReleaseOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->clickReleaseOnEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendHoverEnterEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->hoverEnterEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendHoverOverEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->hoverOverEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::sendHoverLeaveEntity(QUuid id, PointerEvent event) {
|
||||||
|
EntityItemID entityItemID(id);
|
||||||
|
emit getEntities()->hoverLeaveEntity(entityItemID, event);
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include <ViewFrustum.h>
|
#include <ViewFrustum.h>
|
||||||
#include <AbstractUriHandler.h>
|
#include <AbstractUriHandler.h>
|
||||||
#include <shared/RateCounter.h>
|
#include <shared/RateCounter.h>
|
||||||
|
#include <ThreadSafeValueCache.h>
|
||||||
|
|
||||||
#include "avatar/MyAvatar.h"
|
#include "avatar/MyAvatar.h"
|
||||||
#include "Bookmarks.h"
|
#include "Bookmarks.h"
|
||||||
|
@ -254,6 +255,18 @@ public:
|
||||||
gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; }
|
gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; }
|
||||||
gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; }
|
gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; }
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendMousePressOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendMouseMoveOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendMouseReleaseOnEntity(QUuid id, PointerEvent event);
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendClickDownOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendHoldingClickOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendClickReleaseOnEntity(QUuid id, PointerEvent event);
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendHoverEnterEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendHoverOverEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendHoverLeaveEntity(QUuid id, PointerEvent event);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void svoImportRequested(const QString& url);
|
void svoImportRequested(const QString& url);
|
||||||
|
|
||||||
|
@ -319,6 +332,10 @@ public slots:
|
||||||
|
|
||||||
static void runTests();
|
static void runTests();
|
||||||
|
|
||||||
|
QUuid getKeyboardFocusEntity() const; // thread-safe
|
||||||
|
void setKeyboardFocusEntity(QUuid id);
|
||||||
|
void setKeyboardFocusEntity(EntityItemID entityItemID);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void showDesktop();
|
void showDesktop();
|
||||||
void clearDomainOctreeDetails();
|
void clearDomainOctreeDetails();
|
||||||
|
@ -531,7 +548,7 @@ private:
|
||||||
|
|
||||||
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
|
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
|
||||||
|
|
||||||
EntityItemID _keyboardFocusedItem;
|
ThreadSafeValueCache<EntityItemID> _keyboardFocusedItem;
|
||||||
quint64 _lastAcceptedKeyPress = 0;
|
quint64 _lastAcceptedKeyPress = 0;
|
||||||
bool _isForeground = true; // starts out assumed to be in foreground
|
bool _isForeground = true; // starts out assumed to be in foreground
|
||||||
bool _inPaint = false;
|
bool _inPaint = false;
|
||||||
|
|
|
@ -74,8 +74,8 @@ void Web3DOverlay::render(RenderArgs* args) {
|
||||||
if (!_webSurface) {
|
if (!_webSurface) {
|
||||||
_webSurface = new OffscreenQmlSurface();
|
_webSurface = new OffscreenQmlSurface();
|
||||||
_webSurface->create(currentContext);
|
_webSurface->create(currentContext);
|
||||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||||
_webSurface->load("WebEntity.qml");
|
_webSurface->load("WebView.qml");
|
||||||
_webSurface->resume();
|
_webSurface->resume();
|
||||||
_webSurface->getRootItem()->setProperty("url", _url);
|
_webSurface->getRootItem()->setProperty("url", _url);
|
||||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||||
|
|
|
@ -48,7 +48,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
OctreeRenderer(),
|
OctreeRenderer(),
|
||||||
_wantScripts(wantScripts),
|
_wantScripts(wantScripts),
|
||||||
_entitiesScriptEngine(NULL),
|
_entitiesScriptEngine(NULL),
|
||||||
_lastMouseEventValid(false),
|
_lastPointerEventValid(false),
|
||||||
_viewState(viewState),
|
_viewState(viewState),
|
||||||
_scriptingServices(scriptingServices),
|
_scriptingServices(scriptingServices),
|
||||||
_displayModelBounds(false),
|
_displayModelBounds(false),
|
||||||
|
@ -195,9 +195,9 @@ void EntityTreeRenderer::update() {
|
||||||
// Even if we're not moving the mouse, if we started clicking on an entity and we have
|
// Even if we're not moving the mouse, if we started clicking on an entity and we have
|
||||||
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
||||||
// and we want to simulate this message here as well as in mouse move
|
// and we want to simulate this message here as well as in mouse move
|
||||||
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
if (_lastPointerEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastPointerEvent);
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent);
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastPointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -599,18 +599,10 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) {
|
void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) {
|
||||||
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface,
|
|
||||||
[=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event){
|
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
|
||||||
entityScriptingInterface->mousePressOnEntity(intersection.entityID, MouseEvent(*event));
|
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
|
||||||
});
|
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
|
||||||
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface,
|
|
||||||
[=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event) {
|
|
||||||
entityScriptingInterface->mouseMoveOnEntity(intersection.entityID, MouseEvent(*event));
|
|
||||||
});
|
|
||||||
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface,
|
|
||||||
[=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event) {
|
|
||||||
entityScriptingInterface->mouseReleaseOnEntity(intersection.entityID, MouseEvent(*event));
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity);
|
connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity);
|
||||||
connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity);
|
connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity);
|
||||||
|
@ -627,6 +619,59 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
|
||||||
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
|
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static glm::vec2 projectOntoEntityXYPlane(EntityItemPointer entity, const PickRay& pickRay, const RayToEntityIntersectionResult& rayPickResult) {
|
||||||
|
|
||||||
|
if (entity) {
|
||||||
|
|
||||||
|
glm::vec3 entityPosition = entity->getPosition();
|
||||||
|
glm::quat entityRotation = entity->getRotation();
|
||||||
|
glm::vec3 entityDimensions = entity->getDimensions();
|
||||||
|
glm::vec3 entityRegistrationPoint = entity->getRegistrationPoint();
|
||||||
|
|
||||||
|
// project the intersection point onto the local xy plane of the object.
|
||||||
|
float distance;
|
||||||
|
glm::vec3 planePosition = entityPosition;
|
||||||
|
glm::vec3 planeNormal = entityRotation * Vectors::UNIT_Z;
|
||||||
|
glm::vec3 rayDirection = pickRay.direction;
|
||||||
|
glm::vec3 rayStart = pickRay.origin;
|
||||||
|
glm::vec3 p;
|
||||||
|
if (rayPlaneIntersection(planePosition, planeNormal, rayStart, rayDirection, distance)) {
|
||||||
|
p = rayStart + rayDirection * distance;
|
||||||
|
} else {
|
||||||
|
p = rayPickResult.intersection;
|
||||||
|
}
|
||||||
|
glm::vec3 localP = glm::inverse(entityRotation) * (p - entityPosition);
|
||||||
|
glm::vec3 normalizedP = (localP / entityDimensions) + entityRegistrationPoint;
|
||||||
|
return glm::vec2(normalizedP.x * entityDimensions.x,
|
||||||
|
(1.0f - normalizedP.y) * entityDimensions.y); // flip y-axis
|
||||||
|
} else {
|
||||||
|
return glm::vec2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t toPointerButtons(const QMouseEvent& event) {
|
||||||
|
uint32_t buttons = 0;
|
||||||
|
buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0;
|
||||||
|
buttons |= event.buttons().testFlag(Qt::RightButton) ? PointerEvent::SecondaryButton : 0;
|
||||||
|
buttons |= event.buttons().testFlag(Qt::MiddleButton) ? PointerEvent::TertiaryButton : 0;
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
|
||||||
|
switch (event.button()) {
|
||||||
|
case Qt::LeftButton:
|
||||||
|
return PointerEvent::PrimaryButton;
|
||||||
|
case Qt::RightButton:
|
||||||
|
return PointerEvent::SecondaryButton;
|
||||||
|
case Qt::MiddleButton:
|
||||||
|
return PointerEvent::TertiaryButton;
|
||||||
|
default:
|
||||||
|
return PointerEvent::NoButtons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint32_t MOUSE_POINTER_ID = 0;
|
||||||
|
|
||||||
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
|
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
|
||||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||||
// process these events.
|
// process these events.
|
||||||
|
@ -645,24 +690,32 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
|
||||||
QUrl url = QUrl(urlString, QUrl::StrictMode);
|
QUrl url = QUrl(urlString, QUrl::StrictMode);
|
||||||
if (url.isValid() && !url.isEmpty()){
|
if (url.isValid() && !url.isEmpty()){
|
||||||
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
|
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit mousePressOnEntity(rayPickResult, event);
|
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit mousePressOnEntity(rayPickResult.entityID, pointerEvent);
|
||||||
|
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentClickingOnEntityID = rayPickResult.entityID;
|
_currentClickingOnEntityID = rayPickResult.entityID;
|
||||||
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event));
|
emit clickDownOnEntity(_currentClickingOnEntityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastPointerEvent = pointerEvent;
|
||||||
|
_lastPointerEventValid = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
emit mousePressOffEntity(rayPickResult, event);
|
emit mousePressOffEntity();
|
||||||
}
|
}
|
||||||
_lastMouseEvent = MouseEvent(*event);
|
|
||||||
_lastMouseEventValid = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
|
@ -671,31 +724,48 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
if (!_tree || _shuttingDown) {
|
if (!_tree || _shuttingDown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent");
|
PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent");
|
||||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||||
bool precisionPicking = !_dontDoPrecisionPicking;
|
bool precisionPicking = !_dontDoPrecisionPicking;
|
||||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking);
|
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking);
|
||||||
if (rayPickResult.intersects) {
|
if (rayPickResult.intersects) {
|
||||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||||
emit mouseReleaseOnEntity(rayPickResult, event);
|
|
||||||
|
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit mouseReleaseOnEntity(rayPickResult.entityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastPointerEvent = pointerEvent;
|
||||||
|
_lastPointerEventValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
||||||
// we're releasing the button, then this is considered a clickOn event
|
// we're releasing the button, then this is considered a clickOn event
|
||||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event));
|
|
||||||
|
auto entity = getTree()->findEntityByID(_currentClickingOnEntityID);
|
||||||
|
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit clickReleaseOnEntity(_currentClickingOnEntityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes it the unknown ID, we just released so we can't be clicking on anything
|
// makes it the unknown ID, we just released so we can't be clicking on anything
|
||||||
_currentClickingOnEntityID = UNKNOWN_ENTITY_ID;
|
_currentClickingOnEntityID = UNKNOWN_ENTITY_ID;
|
||||||
_lastMouseEvent = MouseEvent(*event);
|
|
||||||
_lastMouseEventValid = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
||||||
|
@ -712,19 +782,35 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
||||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
||||||
if (rayPickResult.intersects) {
|
if (rayPickResult.intersects) {
|
||||||
|
|
||||||
|
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit mouseMoveOnEntity(rayPickResult.entityID, pointerEvent);
|
||||||
|
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", pointerEvent);
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the hover logic...
|
// handle the hover logic...
|
||||||
|
|
||||||
// if we were previously hovering over an entity, and this new entity is not the same as our previous entity
|
// if we were previously hovering over an entity, and this new entity is not the same as our previous entity
|
||||||
// then we need to send the hover leave.
|
// then we need to send the hover leave.
|
||||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event));
|
|
||||||
|
auto entity = getTree()->findEntityByID(_currentHoverOverEntityID);
|
||||||
|
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit hoverLeaveEntity(_currentHoverOverEntityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -732,28 +818,39 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
||||||
// this is true if the _currentHoverOverEntityID is known or unknown
|
// this is true if the _currentHoverOverEntityID is known or unknown
|
||||||
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
|
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
|
||||||
// we should send our hover over event
|
// we should send our hover over event
|
||||||
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event));
|
emit hoverOverEntity(rayPickResult.entityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember what we're hovering over
|
// remember what we're hovering over
|
||||||
_currentHoverOverEntityID = rayPickResult.entityID;
|
_currentHoverOverEntityID = rayPickResult.entityID;
|
||||||
|
|
||||||
|
_lastPointerEvent = pointerEvent;
|
||||||
|
_lastPointerEventValid = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// handle the hover logic...
|
// handle the hover logic...
|
||||||
// if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to
|
// if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to
|
||||||
// send the hover leave for our previous entity
|
// send the hover leave for our previous entity
|
||||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event));
|
|
||||||
|
auto entity = getTree()->findEntityByID(_currentHoverOverEntityID);
|
||||||
|
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit hoverLeaveEntity(_currentHoverOverEntityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
||||||
}
|
}
|
||||||
|
@ -762,13 +859,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
||||||
// Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have
|
// Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have
|
||||||
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
||||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event));
|
|
||||||
|
auto entity = getTree()->findEntityByID(_currentClickingOnEntityID);
|
||||||
|
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
emit holdingClickOnEntity(_currentClickingOnEntityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event));
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lastMouseEvent = MouseEvent(*event);
|
|
||||||
_lastMouseEventValid = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
#include <AbstractAudioInterface.h>
|
#include <AbstractAudioInterface.h>
|
||||||
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
|
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
|
||||||
#include <EntityTree.h>
|
#include <EntityTree.h>
|
||||||
#include <MouseEvent.h>
|
#include <QMouseEvent>
|
||||||
|
#include <PointerEvent.h>
|
||||||
#include <OctreeRenderer.h>
|
#include <OctreeRenderer.h>
|
||||||
#include <ScriptCache.h>
|
#include <ScriptCache.h>
|
||||||
#include <TextureCache.h>
|
#include <TextureCache.h>
|
||||||
|
@ -98,18 +99,18 @@ public:
|
||||||
std::shared_ptr<ZoneEntityItem> myAvatarZone() { return _bestZone; }
|
std::shared_ptr<ZoneEntityItem> myAvatarZone() { return _bestZone; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
|
void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
|
void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void mouseMoveOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
|
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void mouseReleaseOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
|
void mousePressOffEntity();
|
||||||
|
|
||||||
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void clickReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
|
|
||||||
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void hoverEnterEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void hoverOverEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void hoverLeaveEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
|
|
||||||
void enterEntity(const EntityItemID& entityItemID);
|
void enterEntity(const EntityItemID& entityItemID);
|
||||||
void leaveEntity(const EntityItemID& entityItemID);
|
void leaveEntity(const EntityItemID& entityItemID);
|
||||||
|
@ -174,8 +175,8 @@ private:
|
||||||
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||||
const EntityItemID& id, const Collision& collision);
|
const EntityItemID& id, const Collision& collision);
|
||||||
|
|
||||||
bool _lastMouseEventValid;
|
bool _lastPointerEventValid;
|
||||||
MouseEvent _lastMouseEvent;
|
PointerEvent _lastPointerEvent;
|
||||||
AbstractViewStateInterface* _viewState;
|
AbstractViewStateInterface* _viewState;
|
||||||
AbstractScriptingServicesInterface* _scriptingServices;
|
AbstractScriptingServicesInterface* _scriptingServices;
|
||||||
bool _displayModelBounds;
|
bool _displayModelBounds;
|
||||||
|
|
|
@ -63,8 +63,8 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
||||||
QSurface * currentSurface = currentContext->surface();
|
QSurface * currentSurface = currentContext->surface();
|
||||||
_webSurface = new OffscreenQmlSurface();
|
_webSurface = new OffscreenQmlSurface();
|
||||||
_webSurface->create(currentContext);
|
_webSurface->create(currentContext);
|
||||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||||
_webSurface->load("WebEntity.qml");
|
_webSurface->load("WebView.qml");
|
||||||
_webSurface->resume();
|
_webSurface->resume();
|
||||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||||
|
@ -73,91 +73,27 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
||||||
// Restore the original GL context
|
// Restore the original GL context
|
||||||
currentContext->makeCurrent(currentSurface);
|
currentContext->makeCurrent(currentSurface);
|
||||||
|
|
||||||
auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event) {
|
auto forwardPointerEvent = [=](const EntityItemID& entityItemID, const PointerEvent& event) {
|
||||||
// Ignore mouse interaction if we're locked
|
if (entityItemID == getID()) {
|
||||||
if (this->getLocked()) {
|
handlePointerEvent(event);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->button() == Qt::MouseButton::RightButton) {
|
|
||||||
if (event->type() == QEvent::MouseButtonPress) {
|
|
||||||
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
|
|
||||||
_lastPress = toGlm(mouseEvent->pos());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intersection.entityID == getID()) {
|
|
||||||
if (event->button() == Qt::MouseButton::RightButton) {
|
|
||||||
if (event->type() == QEvent::MouseButtonRelease) {
|
|
||||||
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
|
|
||||||
ivec2 dist = glm::abs(toGlm(mouseEvent->pos()) - _lastPress);
|
|
||||||
if (!glm::any(glm::greaterThan(dist, ivec2(1)))) {
|
|
||||||
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
|
|
||||||
QMetaObject::invokeMethod(_webSurface->getRootItem(), "goBack");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_lastPress = ivec2(INT_MIN);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME doesn't work... double click events not received
|
|
||||||
if (event->type() == QEvent::MouseButtonDblClick) {
|
|
||||||
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
|
|
||||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->button() == Qt::MouseButton::MiddleButton) {
|
|
||||||
if (event->type() == QEvent::MouseButtonRelease) {
|
|
||||||
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
|
|
||||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map the intersection point to an actual offscreen pixel
|
|
||||||
glm::vec3 point = intersection.intersection;
|
|
||||||
glm::vec3 dimensions = getDimensions();
|
|
||||||
point -= getPosition();
|
|
||||||
point = glm::inverse(getRotation()) * point;
|
|
||||||
point /= dimensions;
|
|
||||||
point += 0.5f;
|
|
||||||
point.y = 1.0f - point.y;
|
|
||||||
point *= dimensions * (METERS_TO_INCHES * DPI);
|
|
||||||
|
|
||||||
if (event->button() == Qt::MouseButton::LeftButton) {
|
|
||||||
if (event->type() == QEvent::MouseButtonPress) {
|
|
||||||
this->_pressed = true;
|
|
||||||
this->_lastMove = ivec2((int)point.x, (int)point.y);
|
|
||||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
|
||||||
this->_pressed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event->type() == QEvent::MouseMove) {
|
|
||||||
this->_lastMove = ivec2((int)point.x, (int)point.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward the mouse event.
|
|
||||||
QMouseEvent mappedEvent(event->type(),
|
|
||||||
QPoint((int)point.x, (int)point.y),
|
|
||||||
event->screenPos(), event->button(),
|
|
||||||
event->buttons(), event->modifiers());
|
|
||||||
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent);
|
_mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardPointerEvent);
|
||||||
_mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent);
|
_mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardPointerEvent);
|
||||||
_mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent);
|
_mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardPointerEvent);
|
||||||
_hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
_hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const PointerEvent& event) {
|
||||||
if (this->_pressed && this->getID() == entityItemID) {
|
if (this->_pressed && this->getID() == entityItemID) {
|
||||||
// If the user mouses off the entity while the button is down, simulate a mouse release
|
// If the user mouses off the entity while the button is down, simulate a touch end.
|
||||||
QMouseEvent mappedEvent(QEvent::MouseButtonRelease,
|
QTouchEvent::TouchPoint point;
|
||||||
QPoint(_lastMove.x, _lastMove.y),
|
point.setId(event.getID());
|
||||||
Qt::MouseButton::LeftButton,
|
point.setState(Qt::TouchPointReleased);
|
||||||
Qt::MouseButtons(), Qt::KeyboardModifiers());
|
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
|
||||||
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
|
QPointF windowPoint(windowPos.x, windowPos.y);
|
||||||
|
point.setPos(windowPoint);
|
||||||
|
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||||
|
touchPoints.push_back(point);
|
||||||
|
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints);
|
||||||
|
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
@ -242,6 +178,69 @@ QObject* RenderableWebEntityItem::getEventHandler() {
|
||||||
return _webSurface->getEventHandler();
|
return _webSurface->getEventHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
|
||||||
|
|
||||||
|
// Ignore mouse interaction if we're locked
|
||||||
|
if (getLocked() || !_webSurface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
|
||||||
|
QPointF windowPoint(windowPos.x, windowPos.y);
|
||||||
|
|
||||||
|
if (event.getType() == PointerEvent::Move) {
|
||||||
|
// Forward a mouse move event to webSurface
|
||||||
|
QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Forward a touch update event to webSurface
|
||||||
|
if (event.getType() == PointerEvent::Press) {
|
||||||
|
this->_pressed = true;
|
||||||
|
} else if (event.getType() == PointerEvent::Release) {
|
||||||
|
this->_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QEvent::Type type;
|
||||||
|
Qt::TouchPointState touchPointState;
|
||||||
|
switch (event.getType()) {
|
||||||
|
case PointerEvent::Press:
|
||||||
|
type = QEvent::TouchBegin;
|
||||||
|
touchPointState = Qt::TouchPointPressed;
|
||||||
|
break;
|
||||||
|
case PointerEvent::Release:
|
||||||
|
type = QEvent::TouchEnd;
|
||||||
|
touchPointState = Qt::TouchPointReleased;
|
||||||
|
break;
|
||||||
|
case PointerEvent::Move:
|
||||||
|
default:
|
||||||
|
type = QEvent::TouchUpdate;
|
||||||
|
touchPointState = Qt::TouchPointMoved;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTouchEvent::TouchPoint point;
|
||||||
|
point.setId(event.getID());
|
||||||
|
point.setState(touchPointState);
|
||||||
|
point.setPos(windowPoint);
|
||||||
|
point.setScreenPos(windowPoint);
|
||||||
|
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||||
|
touchPoints.push_back(point);
|
||||||
|
|
||||||
|
QTouchEvent* touchEvent = new QTouchEvent(type);
|
||||||
|
touchEvent->setWindow(nullptr);
|
||||||
|
touchEvent->setDevice(nullptr);
|
||||||
|
touchEvent->setTarget(nullptr);
|
||||||
|
touchEvent->setTouchPoints(touchPoints);
|
||||||
|
touchEvent->setTouchPointStates(touchPointState);
|
||||||
|
|
||||||
|
_lastTouchEvent = *touchEvent;
|
||||||
|
|
||||||
|
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RenderableWebEntityItem::destroyWebSurface() {
|
void RenderableWebEntityItem::destroyWebSurface() {
|
||||||
if (_webSurface) {
|
if (_webSurface) {
|
||||||
--_currentWebCount;
|
--_currentWebCount;
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
#define hifi_RenderableWebEntityItem_h
|
#define hifi_RenderableWebEntityItem_h
|
||||||
|
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QTouchEvent>
|
||||||
|
#include <PointerEvent.h>
|
||||||
|
|
||||||
#include <WebEntityItem.h>
|
#include <WebEntityItem.h>
|
||||||
|
|
||||||
|
@ -28,9 +31,13 @@ public:
|
||||||
|
|
||||||
virtual void render(RenderArgs* args) override;
|
virtual void render(RenderArgs* args) override;
|
||||||
virtual void setSourceUrl(const QString& value) override;
|
virtual void setSourceUrl(const QString& value) override;
|
||||||
|
|
||||||
void setProxyWindow(QWindow* proxyWindow);
|
virtual bool wantsHandControllerPointerEvents() const override { return true; }
|
||||||
QObject* getEventHandler();
|
virtual bool wantsKeyboardFocus() const override { return true; }
|
||||||
|
virtual void setProxyWindow(QWindow* proxyWindow) override;
|
||||||
|
virtual QObject* getEventHandler() override;
|
||||||
|
|
||||||
|
void handlePointerEvent(const PointerEvent& event);
|
||||||
|
|
||||||
void update(const quint64& now) override;
|
void update(const quint64& now) override;
|
||||||
bool needsToCallUpdate() const override { return _webSurface != nullptr; }
|
bool needsToCallUpdate() const override { return _webSurface != nullptr; }
|
||||||
|
@ -46,7 +53,7 @@ private:
|
||||||
uint32_t _texture{ 0 };
|
uint32_t _texture{ 0 };
|
||||||
ivec2 _lastPress{ INT_MIN };
|
ivec2 _lastPress{ INT_MIN };
|
||||||
bool _pressed{ false };
|
bool _pressed{ false };
|
||||||
ivec2 _lastMove{ INT_MIN };
|
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
||||||
uint64_t _lastRenderTime{ 0 };
|
uint64_t _lastRenderTime{ 0 };
|
||||||
|
|
||||||
QMetaObject::Connection _mousePressConnection;
|
QMetaObject::Connection _mousePressConnection;
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
|
||||||
#include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes
|
#include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes
|
||||||
#include <Octree.h> // for EncodeBitstreamParams class
|
#include <Octree.h> // for EncodeBitstreamParams class
|
||||||
#include <OctreeElement.h> // for OctreeElement::AppendState
|
#include <OctreeElement.h> // for OctreeElement::AppendState
|
||||||
|
@ -436,6 +438,11 @@ public:
|
||||||
static std::function<bool()> getEntitiesShouldFadeFunction() { return _entitiesShouldFadeFunction; }
|
static std::function<bool()> getEntitiesShouldFadeFunction() { return _entitiesShouldFadeFunction; }
|
||||||
virtual bool isTransparent() { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; }
|
virtual bool isTransparent() { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; }
|
||||||
|
|
||||||
|
virtual bool wantsHandControllerPointerEvents() const { return false; }
|
||||||
|
virtual bool wantsKeyboardFocus() const { return false; }
|
||||||
|
virtual void setProxyWindow(QWindow* proxyWindow) {}
|
||||||
|
virtual QObject* getEventHandler() { return nullptr; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void setSimulated(bool simulated) { _simulated = simulated; }
|
void setSimulated(bool simulated) { _simulated = simulated; }
|
||||||
|
|
|
@ -1230,6 +1230,65 @@ QVector<QUuid> EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUuid EntityScriptingInterface::getKeyboardFocusEntity() const {
|
||||||
|
QUuid result;
|
||||||
|
QMetaObject::invokeMethod(qApp, "getKeyboardFocusEntity", Qt::DirectConnection, Q_RETURN_ARG(QUuid, result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::setKeyboardFocusEntity(QUuid id) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "setKeyboardFocusEntity", Qt::QueuedConnection, Q_ARG(QUuid, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendMousePressOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendMousePressOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendMouseMoveOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendMouseMoveOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendMouseReleaseOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendMouseReleaseOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendClickDownOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendClickDownOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendHoldingClickOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendHoldingClickOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendClickReleaseOnEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendClickReleaseOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendHoverEnterEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendHoverEnterEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendHoverOverEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendHoverOverEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::sendHoverLeaveEntity(QUuid id, PointerEvent event) {
|
||||||
|
QMetaObject::invokeMethod(qApp, "sendHoverLeaveEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntityScriptingInterface::wantsHandControllerPointerEvents(QUuid id) {
|
||||||
|
bool result = false;
|
||||||
|
if (_entityTree) {
|
||||||
|
_entityTree->withReadLock([&] {
|
||||||
|
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(id));
|
||||||
|
if (entity) {
|
||||||
|
result = entity->wantsHandControllerPointerEvents();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) {
|
float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) {
|
||||||
return std::abs(mass * (newVelocity - oldVelocity));
|
return std::abs(mass * (newVelocity - oldVelocity));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <Octree.h>
|
#include <Octree.h>
|
||||||
#include <OctreeScriptingInterface.h>
|
#include <OctreeScriptingInterface.h>
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
|
#include <PointerEvent.h>
|
||||||
|
|
||||||
#include "PolyVoxEntityItem.h"
|
#include "PolyVoxEntityItem.h"
|
||||||
#include "LineEntityItem.h"
|
#include "LineEntityItem.h"
|
||||||
|
@ -34,7 +35,6 @@
|
||||||
#include "EntityItemProperties.h"
|
#include "EntityItemProperties.h"
|
||||||
|
|
||||||
class EntityTree;
|
class EntityTree;
|
||||||
class MouseEvent;
|
|
||||||
|
|
||||||
class RayToEntityIntersectionResult {
|
class RayToEntityIntersectionResult {
|
||||||
public:
|
public:
|
||||||
|
@ -62,6 +62,8 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende
|
||||||
|
|
||||||
Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy)
|
Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy)
|
||||||
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
|
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
|
||||||
|
Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EntityScriptingInterface(bool bidOnSimulationOwnership);
|
EntityScriptingInterface(bool bidOnSimulationOwnership);
|
||||||
|
|
||||||
|
@ -186,6 +188,23 @@ public slots:
|
||||||
Q_INVOKABLE QVector<QUuid> getChildrenIDs(const QUuid& parentID);
|
Q_INVOKABLE QVector<QUuid> getChildrenIDs(const QUuid& parentID);
|
||||||
Q_INVOKABLE QVector<QUuid> getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex);
|
Q_INVOKABLE QVector<QUuid> getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex);
|
||||||
|
|
||||||
|
Q_INVOKABLE QUuid getKeyboardFocusEntity() const;
|
||||||
|
Q_INVOKABLE void setKeyboardFocusEntity(QUuid id);
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendMousePressOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendMouseMoveOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendMouseReleaseOnEntity(QUuid id, PointerEvent event);
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendClickDownOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendHoldingClickOnEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendClickReleaseOnEntity(QUuid id, PointerEvent event);
|
||||||
|
|
||||||
|
Q_INVOKABLE void sendHoverEnterEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendHoverOverEntity(QUuid id, PointerEvent event);
|
||||||
|
Q_INVOKABLE void sendHoverLeaveEntity(QUuid id, PointerEvent event);
|
||||||
|
|
||||||
|
Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||||
|
|
||||||
|
@ -193,17 +212,17 @@ signals:
|
||||||
void canRezChanged(bool canRez);
|
void canRezChanged(bool canRez);
|
||||||
void canRezTmpChanged(bool canRez);
|
void canRezTmpChanged(bool canRez);
|
||||||
|
|
||||||
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
|
|
||||||
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void clickReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
|
|
||||||
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void hoverEnterEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void hoverOverEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void hoverLeaveEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
|
|
||||||
void enterEntity(const EntityItemID& entityItemID);
|
void enterEntity(const EntityItemID& entityItemID);
|
||||||
void leaveEntity(const EntityItemID& entityItemID);
|
void leaveEntity(const EntityItemID& entityItemID);
|
||||||
|
|
|
@ -61,7 +61,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
}
|
}
|
||||||
setLastEdited(properties._lastEdited);
|
setLastEdited(properties._lastEdited);
|
||||||
}
|
}
|
||||||
|
|
||||||
return somethingChanged;
|
return somethingChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,27 +91,32 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
|
||||||
EntityPropertyFlags& requestedProperties,
|
EntityPropertyFlags& requestedProperties,
|
||||||
EntityPropertyFlags& propertyFlags,
|
EntityPropertyFlags& propertyFlags,
|
||||||
EntityPropertyFlags& propertiesDidntFit,
|
EntityPropertyFlags& propertiesDidntFit,
|
||||||
int& propertyCount,
|
int& propertyCount,
|
||||||
OctreeElement::AppendState& appendState) const {
|
OctreeElement::AppendState& appendState) const {
|
||||||
|
|
||||||
bool successPropertyFits = true;
|
bool successPropertyFits = true;
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
|
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal,
|
BoxFace& face, glm::vec3& surfaceNormal,
|
||||||
void** intersectedObject, bool precisionPicking) const {
|
void** intersectedObject, bool precisionPicking) const {
|
||||||
glm::vec3 dimensions = getDimensions();
|
glm::vec3 dimensions = getDimensions();
|
||||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||||
glm::quat rotation = getRotation();
|
glm::quat rotation = getRotation();
|
||||||
glm::vec3 position = getPosition() + rotation *
|
glm::vec3 position = getPosition() + rotation * (dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
|
||||||
(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
|
|
||||||
// FIXME - should set face and surfaceNormal
|
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
|
||||||
return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance);
|
surfaceNormal = rotation * Vectors::UNIT_Z;
|
||||||
|
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityItem::setSourceUrl(const QString& value) {
|
void WebEntityItem::setSourceUrl(const QString& value) {
|
||||||
if (_sourceUrl != value) {
|
if (_sourceUrl != value) {
|
||||||
_sourceUrl = value;
|
_sourceUrl = value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <Finally.h>
|
#include <Finally.h>
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
|
#include <AbstractUriHandler.h>
|
||||||
|
#include <AccountManager.h>
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
|
|
||||||
#include "OffscreenGLCanvas.h"
|
#include "OffscreenGLCanvas.h"
|
||||||
|
@ -32,6 +34,39 @@
|
||||||
#include "GLHelpers.h"
|
#include "GLHelpers.h"
|
||||||
|
|
||||||
|
|
||||||
|
QString fixupHifiUrl(const QString& urlString) {
|
||||||
|
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
||||||
|
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
|
||||||
|
QUrl url(urlString);
|
||||||
|
QUrlQuery query(url);
|
||||||
|
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
|
||||||
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
|
||||||
|
url.setQuery(query.query());
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
return urlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UrlHandler : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE bool canHandleUrl(const QString& url) {
|
||||||
|
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
|
||||||
|
return handler->canAcceptURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE bool handleUrl(const QString& url) {
|
||||||
|
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
|
||||||
|
return handler->acceptURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME hack for authentication, remove when we migrate to Qt 5.6
|
||||||
|
Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {
|
||||||
|
return fixupHifiUrl(originalUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Time between receiving a request to render the offscreen UI actually triggering
|
// Time between receiving a request to render the offscreen UI actually triggering
|
||||||
// the render. Could possibly be increased depending on the framerate we expect to
|
// the render. Could possibly be increased depending on the framerate we expect to
|
||||||
// achieve.
|
// achieve.
|
||||||
|
@ -438,6 +473,9 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &OffscreenQmlSurface::onAboutToQuit);
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &OffscreenQmlSurface::onAboutToQuit);
|
||||||
_updateTimer.setInterval(MIN_TIMER_MS);
|
_updateTimer.setInterval(MIN_TIMER_MS);
|
||||||
_updateTimer.start();
|
_updateTimer.start();
|
||||||
|
|
||||||
|
auto rootContext = getRootContext();
|
||||||
|
rootContext->setContextProperty("urlHandler", new UrlHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
||||||
|
@ -786,3 +824,5 @@ void OffscreenQmlSurface::setFocusText(bool newFocusText) {
|
||||||
emit focusTextChanged(_focusText);
|
emit focusTextChanged(_focusText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "OffscreenQmlSurface.moc"
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "KeyEvent.h"
|
#include "KeyEvent.h"
|
||||||
#include "MouseEvent.h"
|
#include "MouseEvent.h"
|
||||||
#include "SpatialEvent.h"
|
#include "SpatialEvent.h"
|
||||||
|
#include "PointerEvent.h"
|
||||||
#include "TouchEvent.h"
|
#include "TouchEvent.h"
|
||||||
#include "WheelEvent.h"
|
#include "WheelEvent.h"
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ void registerEventTypes(QScriptEngine* engine) {
|
||||||
qScriptRegisterMetaType(engine, HFActionEvent::toScriptValue, HFActionEvent::fromScriptValue);
|
qScriptRegisterMetaType(engine, HFActionEvent::toScriptValue, HFActionEvent::fromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, KeyEvent::toScriptValue, KeyEvent::fromScriptValue);
|
qScriptRegisterMetaType(engine, KeyEvent::toScriptValue, KeyEvent::fromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, MouseEvent::toScriptValue, MouseEvent::fromScriptValue);
|
qScriptRegisterMetaType(engine, MouseEvent::toScriptValue, MouseEvent::fromScriptValue);
|
||||||
|
qScriptRegisterMetaType(engine, PointerEvent::toScriptValue, PointerEvent::fromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, TouchEvent::toScriptValue, TouchEvent::fromScriptValue);
|
qScriptRegisterMetaType(engine, TouchEvent::toScriptValue, TouchEvent::fromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, WheelEvent::toScriptValue, WheelEvent::fromScriptValue);
|
qScriptRegisterMetaType(engine, WheelEvent::toScriptValue, WheelEvent::fromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, SpatialEvent::toScriptValue, SpatialEvent::fromScriptValue);
|
qScriptRegisterMetaType(engine, SpatialEvent::toScriptValue, SpatialEvent::fromScriptValue);
|
||||||
|
|
|
@ -723,9 +723,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>;
|
using PointerHandler = std::function<void(const EntityItemID&, const PointerEvent&)>;
|
||||||
auto makeMouseHandler = [this](QString eventName) -> MouseHandler {
|
auto makePointerHandler = [this](QString eventName) -> PointerHandler {
|
||||||
return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) {
|
return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) {
|
||||||
forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
|
forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -741,17 +741,17 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));
|
connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));
|
connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));
|
||||||
|
|
||||||
connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makeMouseHandler("mousePressOnEntity"));
|
connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makePointerHandler("mousePressOnEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makeMouseHandler("mouseMoveOnEntity"));
|
connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makePointerHandler("mouseMoveOnEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makeMouseHandler("mouseReleaseOnEntity"));
|
connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makePointerHandler("mouseReleaseOnEntity"));
|
||||||
|
|
||||||
connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makeMouseHandler("clickDownOnEntity"));
|
connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makePointerHandler("clickDownOnEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makeMouseHandler("holdingClickOnEntity"));
|
connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makePointerHandler("holdingClickOnEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makeMouseHandler("clickReleaseOnEntity"));
|
connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makePointerHandler("clickReleaseOnEntity"));
|
||||||
|
|
||||||
connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makeMouseHandler("hoverEnterEntity"));
|
connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makePointerHandler("hoverEnterEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity"));
|
connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makePointerHandler("hoverOverEntity"));
|
||||||
connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity"));
|
connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makePointerHandler("hoverLeaveEntity"));
|
||||||
|
|
||||||
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity"));
|
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity"));
|
||||||
}
|
}
|
||||||
|
@ -1546,7 +1546,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) {
|
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
#ifdef THREAD_DEBUGGING
|
#ifdef THREAD_DEBUGGING
|
||||||
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
@ -1556,12 +1556,12 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
||||||
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||||
Q_ARG(const EntityItemID&, entityID),
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
Q_ARG(const QString&, methodName),
|
Q_ARG(const QString&, methodName),
|
||||||
Q_ARG(const MouseEvent&, event));
|
Q_ARG(const PointerEvent&, event));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef THREAD_DEBUGGING
|
#ifdef THREAD_DEBUGGING
|
||||||
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
|
"entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
refreshFileScript(entityID);
|
refreshFileScript(entityID);
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
#include <EntityItemID.h>
|
#include <EntityItemID.h>
|
||||||
#include <EntitiesScriptEngineProvider.h>
|
#include <EntitiesScriptEngineProvider.h>
|
||||||
|
|
||||||
#include "MouseEvent.h"
|
#include "PointerEvent.h"
|
||||||
#include "ArrayBufferClass.h"
|
#include "ArrayBufferClass.h"
|
||||||
#include "AssetScriptingInterface.h"
|
#include "AssetScriptingInterface.h"
|
||||||
#include "AudioScriptingInterface.h"
|
#include "AudioScriptingInterface.h"
|
||||||
|
@ -140,7 +140,7 @@ public:
|
||||||
Q_INVOKABLE void unloadAllEntityScripts();
|
Q_INVOKABLE void unloadAllEntityScripts();
|
||||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName,
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName,
|
||||||
const QStringList& params = QStringList()) override;
|
const QStringList& params = QStringList()) override;
|
||||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event);
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event);
|
||||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision);
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision);
|
||||||
|
|
||||||
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
|
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
|
||||||
|
|
|
@ -113,6 +113,20 @@ int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk)
|
||||||
// calculate the angle between a point on a sphere that is closest to the cone.
|
// calculate the angle between a point on a sphere that is closest to the cone.
|
||||||
float coneSphereAngle(const glm::vec3& coneCenter, const glm::vec3& coneDirection, const glm::vec3& sphereCenter, float sphereRadius);
|
float coneSphereAngle(const glm::vec3& coneCenter, const glm::vec3& coneDirection, const glm::vec3& sphereCenter, float sphereRadius);
|
||||||
|
|
||||||
|
inline bool rayPlaneIntersection(const glm::vec3& planePosition, const glm::vec3& planeNormal,
|
||||||
|
const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distanceOut) {
|
||||||
|
float rayDirectionDotPlaneNormal = glm::dot(rayDirection, planeNormal);
|
||||||
|
const float PARALLEL_THRESHOLD = 0.0001f;
|
||||||
|
if (fabsf(rayDirectionDotPlaneNormal) > PARALLEL_THRESHOLD) {
|
||||||
|
float rayStartDotPlaneNormal = glm::dot(planePosition - rayStart, planeNormal);
|
||||||
|
distanceOut = rayStartDotPlaneNormal / rayDirectionDotPlaneNormal;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// ray is parallel to the plane
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef glm::vec2 LineSegment2[2];
|
typedef glm::vec2 LineSegment2[2];
|
||||||
|
|
||||||
// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html
|
// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html
|
||||||
|
|
163
libraries/shared/src/PointerEvent.cpp
Normal file
163
libraries/shared/src/PointerEvent.cpp
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
//
|
||||||
|
// PointerEvent.cpp
|
||||||
|
// script-engine/src
|
||||||
|
//
|
||||||
|
// Created by Anthony Thibault on 2016-8-11.
|
||||||
|
// Copyright 2016 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 "PointerEvent.h"
|
||||||
|
|
||||||
|
#include <qscriptengine.h>
|
||||||
|
#include <qscriptvalue.h>
|
||||||
|
|
||||||
|
#include "RegisteredMetaTypes.h"
|
||||||
|
|
||||||
|
static bool areFlagsSet(uint32_t flags, uint32_t mask) {
|
||||||
|
return (flags & mask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEvent::PointerEvent() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEvent::PointerEvent(EventType type, uint32_t id,
|
||||||
|
const glm::vec2& pos2D, const glm::vec3& pos3D,
|
||||||
|
const glm::vec3& normal, const glm::vec3& direction,
|
||||||
|
Button button, uint32_t buttons) :
|
||||||
|
_type(type),
|
||||||
|
_id(id),
|
||||||
|
_pos2D(pos2D),
|
||||||
|
_pos3D(pos3D),
|
||||||
|
_normal(normal),
|
||||||
|
_direction(direction),
|
||||||
|
_button(button),
|
||||||
|
_buttons(buttons)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEvent& event) {
|
||||||
|
QScriptValue obj = engine->newObject();
|
||||||
|
|
||||||
|
switch (event._type) {
|
||||||
|
case Press:
|
||||||
|
obj.setProperty("type", "Press");
|
||||||
|
break;
|
||||||
|
case Release:
|
||||||
|
obj.setProperty("type", "Release");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case Move:
|
||||||
|
obj.setProperty("type", "Move");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
obj.setProperty("id", event._id);
|
||||||
|
|
||||||
|
QScriptValue pos2D = engine->newObject();
|
||||||
|
pos2D.setProperty("x", event._pos2D.x);
|
||||||
|
pos2D.setProperty("y", event._pos2D.y);
|
||||||
|
obj.setProperty("pos2D", pos2D);
|
||||||
|
|
||||||
|
QScriptValue pos3D = engine->newObject();
|
||||||
|
pos3D.setProperty("x", event._pos3D.x);
|
||||||
|
pos3D.setProperty("y", event._pos3D.y);
|
||||||
|
pos3D.setProperty("z", event._pos3D.z);
|
||||||
|
obj.setProperty("pos3D", pos3D);
|
||||||
|
|
||||||
|
QScriptValue normal = engine->newObject();
|
||||||
|
normal.setProperty("x", event._normal.x);
|
||||||
|
normal.setProperty("y", event._normal.y);
|
||||||
|
normal.setProperty("z", event._normal.z);
|
||||||
|
obj.setProperty("pos3D", normal);
|
||||||
|
|
||||||
|
QScriptValue direction = engine->newObject();
|
||||||
|
direction.setProperty("x", event._direction.x);
|
||||||
|
direction.setProperty("y", event._direction.y);
|
||||||
|
direction.setProperty("z", event._direction.z);
|
||||||
|
obj.setProperty("pos3D", direction);
|
||||||
|
|
||||||
|
switch (event._button) {
|
||||||
|
case NoButtons:
|
||||||
|
obj.setProperty("button", "None");
|
||||||
|
break;
|
||||||
|
case PrimaryButton:
|
||||||
|
obj.setProperty("button", "Primary");
|
||||||
|
break;
|
||||||
|
case SecondaryButton:
|
||||||
|
obj.setProperty("button", "Secondary");
|
||||||
|
break;
|
||||||
|
case TertiaryButton:
|
||||||
|
obj.setProperty("button", "Tertiary");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.setProperty("isLeftButton", areFlagsSet(event._buttons, PrimaryButton));
|
||||||
|
obj.setProperty("isRightButton", areFlagsSet(event._buttons, SecondaryButton));
|
||||||
|
obj.setProperty("isMiddleButton", areFlagsSet(event._buttons, TertiaryButton));
|
||||||
|
|
||||||
|
obj.setProperty("isPrimaryButton", areFlagsSet(event._buttons, PrimaryButton));
|
||||||
|
obj.setProperty("isSecondaryButton", areFlagsSet(event._buttons, SecondaryButton));
|
||||||
|
obj.setProperty("isTertiaryButton", areFlagsSet(event._buttons, TertiaryButton));
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& event) {
|
||||||
|
if (object.isObject()) {
|
||||||
|
QScriptValue type = object.property("type");
|
||||||
|
QString typeStr = type.isString() ? type.toString() : "Move";
|
||||||
|
if (typeStr == "Press") {
|
||||||
|
event._type = Press;
|
||||||
|
} else if (typeStr == "Release") {
|
||||||
|
event._type = Release;
|
||||||
|
} else {
|
||||||
|
event._type = Move;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue id = object.property("id");
|
||||||
|
event._id = type.isNumber() ? (uint32_t)type.toNumber() : 0;
|
||||||
|
|
||||||
|
glm::vec2 pos2D;
|
||||||
|
vec2FromScriptValue(object.property("pos2D"), event._pos2D);
|
||||||
|
|
||||||
|
glm::vec3 pos3D;
|
||||||
|
vec3FromScriptValue(object.property("pos3D"), event._pos3D);
|
||||||
|
|
||||||
|
glm::vec3 normal;
|
||||||
|
vec3FromScriptValue(object.property("normal"), event._normal);
|
||||||
|
|
||||||
|
glm::vec3 direction;
|
||||||
|
vec3FromScriptValue(object.property("direction"), event._direction);
|
||||||
|
|
||||||
|
QScriptValue button = object.property("button");
|
||||||
|
QString buttonStr = type.isString() ? type.toString() : "NoButtons";
|
||||||
|
if (buttonStr == "Primary") {
|
||||||
|
event._button = PrimaryButton;
|
||||||
|
} else if (buttonStr == "Secondary") {
|
||||||
|
event._button = SecondaryButton;
|
||||||
|
} else if (buttonStr == "Tertiary") {
|
||||||
|
event._button = TertiaryButton;
|
||||||
|
} else {
|
||||||
|
event._button = NoButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool primary = object.property("isPrimary").toBool() || object.property("isLeftButton").toBool();
|
||||||
|
bool secondary = object.property("isSecondary").toBool() || object.property("isRightButton").toBool();
|
||||||
|
bool tertiary = object.property("isTertiary").toBool() || object.property("isMiddleButton").toBool();
|
||||||
|
event._buttons = 0;
|
||||||
|
if (primary) {
|
||||||
|
event._buttons |= PrimaryButton;
|
||||||
|
}
|
||||||
|
if (secondary) {
|
||||||
|
event._buttons |= SecondaryButton;
|
||||||
|
}
|
||||||
|
if (tertiary) {
|
||||||
|
event._buttons |= TertiaryButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
libraries/shared/src/PointerEvent.h
Normal file
68
libraries/shared/src/PointerEvent.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// PointerEvent.h
|
||||||
|
// script-engine/src
|
||||||
|
//
|
||||||
|
// Created by Anthony Thibault on 2016-8-11.
|
||||||
|
// Copyright 2016 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
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_PointerEvent_h
|
||||||
|
#define hifi_PointerEvent_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <QScriptValue>
|
||||||
|
|
||||||
|
class PointerEvent {
|
||||||
|
public:
|
||||||
|
enum Button {
|
||||||
|
NoButtons = 0x0,
|
||||||
|
PrimaryButton = 0x1,
|
||||||
|
SecondaryButton = 0x2,
|
||||||
|
TertiaryButton = 0x4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EventType {
|
||||||
|
Press, // A button has just been pressed
|
||||||
|
Release, // A button has just been released
|
||||||
|
Move // The pointer has just moved
|
||||||
|
};
|
||||||
|
|
||||||
|
PointerEvent();
|
||||||
|
PointerEvent(EventType type, uint32_t id,
|
||||||
|
const glm::vec2& pos2D, const glm::vec3& pos3D,
|
||||||
|
const glm::vec3& normal, const glm::vec3& direction,
|
||||||
|
Button button, uint32_t buttons);
|
||||||
|
|
||||||
|
static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event);
|
||||||
|
static void fromScriptValue(const QScriptValue& object, PointerEvent& event);
|
||||||
|
|
||||||
|
QScriptValue toScriptValue(QScriptEngine* engine) const { return PointerEvent::toScriptValue(engine, *this); }
|
||||||
|
|
||||||
|
EventType getType() const { return _type; }
|
||||||
|
uint32_t getID() const { return _id; }
|
||||||
|
const glm::vec2& getPos2D() const { return _pos2D; }
|
||||||
|
const glm::vec3& getPos3D() const { return _pos3D; }
|
||||||
|
const glm::vec3& getNormal() const { return _normal; }
|
||||||
|
const glm::vec3& getDirection() const { return _direction; }
|
||||||
|
Button getButton() const { return _button; }
|
||||||
|
uint32_t getButtons() const { return _buttons; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EventType _type;
|
||||||
|
uint32_t _id; // used to identify the pointer. (left vs right hand, for example)
|
||||||
|
glm::vec2 _pos2D; // (in meters) projected onto the xy plane of entities dimension box, (0, 0) is upper right hand corner
|
||||||
|
glm::vec3 _pos3D; // surface location in world coordinates (in meters)
|
||||||
|
glm::vec3 _normal; // surface normal
|
||||||
|
glm::vec3 _direction; // incoming direction of pointer ray.
|
||||||
|
|
||||||
|
Button _button { NoButtons }; // button assosiated with this event, (if type is Press, this will be the button that is pressed)
|
||||||
|
uint32_t _buttons { NoButtons }; // the current state of all the buttons.
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(PointerEvent)
|
||||||
|
|
||||||
|
#endif // hifi_PointerEvent_h
|
|
@ -60,39 +60,6 @@ private:
|
||||||
bool _navigationFocusDisabled{ false };
|
bool _navigationFocusDisabled{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
QString fixupHifiUrl(const QString& urlString) {
|
|
||||||
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
|
||||||
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
|
|
||||||
QUrl url(urlString);
|
|
||||||
QUrlQuery query(url);
|
|
||||||
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
|
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
|
|
||||||
url.setQuery(query.query());
|
|
||||||
return url.toString();
|
|
||||||
}
|
|
||||||
return urlString;
|
|
||||||
}
|
|
||||||
|
|
||||||
class UrlHandler : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Q_INVOKABLE bool canHandleUrl(const QString& url) {
|
|
||||||
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
|
|
||||||
return handler->canAcceptURL(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE bool handleUrl(const QString& url) {
|
|
||||||
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
|
|
||||||
return handler->acceptURL(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME hack for authentication, remove when we migrate to Qt 5.6
|
|
||||||
Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {
|
|
||||||
return fixupHifiUrl(originalUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static OffscreenFlags* offscreenFlags { nullptr };
|
static OffscreenFlags* offscreenFlags { nullptr };
|
||||||
|
|
||||||
// This hack allows the QML UI to work with keys that are also bound as
|
// This hack allows the QML UI to work with keys that are also bound as
|
||||||
|
@ -126,7 +93,6 @@ void OffscreenUi::create(QOpenGLContext* context) {
|
||||||
|
|
||||||
rootContext->setContextProperty("OffscreenUi", this);
|
rootContext->setContextProperty("OffscreenUi", this);
|
||||||
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
|
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
|
||||||
rootContext->setContextProperty("urlHandler", new UrlHandler());
|
|
||||||
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
|
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,7 @@ var PICKS_PER_SECOND_PER_HAND = 60;
|
||||||
var MSECS_PER_SEC = 1000.0;
|
var MSECS_PER_SEC = 1000.0;
|
||||||
var GRABBABLE_PROPERTIES = [
|
var GRABBABLE_PROPERTIES = [
|
||||||
"position",
|
"position",
|
||||||
|
"registrationPoint",
|
||||||
"rotation",
|
"rotation",
|
||||||
"gravity",
|
"gravity",
|
||||||
"collidesWith",
|
"collidesWith",
|
||||||
|
@ -173,6 +174,7 @@ var STATE_NEAR_GRABBING = 3;
|
||||||
var STATE_NEAR_TRIGGER = 4;
|
var STATE_NEAR_TRIGGER = 4;
|
||||||
var STATE_FAR_TRIGGER = 5;
|
var STATE_FAR_TRIGGER = 5;
|
||||||
var STATE_HOLD = 6;
|
var STATE_HOLD = 6;
|
||||||
|
var STATE_ENTITY_TOUCHING = 7;
|
||||||
|
|
||||||
// "collidesWith" is specified by comma-separated list of group names
|
// "collidesWith" is specified by comma-separated list of group names
|
||||||
// the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar
|
// the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar
|
||||||
|
@ -188,6 +190,8 @@ var delayedDeactivateEntityID;
|
||||||
|
|
||||||
var CONTROLLER_STATE_MACHINE = {};
|
var CONTROLLER_STATE_MACHINE = {};
|
||||||
|
|
||||||
|
var mostRecentSearchingHand = RIGHT_HAND;
|
||||||
|
|
||||||
CONTROLLER_STATE_MACHINE[STATE_OFF] = {
|
CONTROLLER_STATE_MACHINE[STATE_OFF] = {
|
||||||
name: "off",
|
name: "off",
|
||||||
enterMethod: "offEnter",
|
enterMethod: "offEnter",
|
||||||
|
@ -195,6 +199,7 @@ CONTROLLER_STATE_MACHINE[STATE_OFF] = {
|
||||||
};
|
};
|
||||||
CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = {
|
CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = {
|
||||||
name: "searching",
|
name: "searching",
|
||||||
|
enterMethod: "searchEnter",
|
||||||
updateMethod: "search"
|
updateMethod: "search"
|
||||||
};
|
};
|
||||||
CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = {
|
CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = {
|
||||||
|
@ -222,6 +227,71 @@ CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = {
|
||||||
enterMethod: "farTriggerEnter",
|
enterMethod: "farTriggerEnter",
|
||||||
updateMethod: "farTrigger"
|
updateMethod: "farTrigger"
|
||||||
};
|
};
|
||||||
|
CONTROLLER_STATE_MACHINE[STATE_ENTITY_TOUCHING] = {
|
||||||
|
name: "entityTouching",
|
||||||
|
enterMethod: "entityTouchingEnter",
|
||||||
|
exitMethod: "entityTouchingExit",
|
||||||
|
updateMethod: "entityTouching"
|
||||||
|
};
|
||||||
|
|
||||||
|
function projectOntoEntityXYPlane(entityID, worldPos) {
|
||||||
|
var props = entityPropertiesCache.getProps(entityID);
|
||||||
|
var invRot = Quat.inverse(props.rotation);
|
||||||
|
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, props.position));
|
||||||
|
var invDimensions = { x: 1 / props.dimensions.x,
|
||||||
|
y: 1 / props.dimensions.y,
|
||||||
|
z: 1 / props.dimensions.z };
|
||||||
|
var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint);
|
||||||
|
return { x: normalizedPos.x * props.dimensions.x,
|
||||||
|
y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis
|
||||||
|
}
|
||||||
|
|
||||||
|
function handLaserIntersectEntity(entityID, hand) {
|
||||||
|
var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
|
var pose = Controller.getPoseValue(standardControllerValue);
|
||||||
|
var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position);
|
||||||
|
var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation);
|
||||||
|
|
||||||
|
var props = entityPropertiesCache.getProps(entityID);
|
||||||
|
|
||||||
|
if (props.position) {
|
||||||
|
var planePosition = props.position;
|
||||||
|
var planeNormal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1.0});
|
||||||
|
var rayStart = worldHandPosition;
|
||||||
|
var rayDirection = Quat.getUp(worldHandRotation);
|
||||||
|
var intersectionInfo = rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection);
|
||||||
|
|
||||||
|
var intersectionPoint = planePosition;
|
||||||
|
if (intersectionInfo.hit && intersectionInfo.distance > 0) {
|
||||||
|
intersectionPoint = Vec3.sum(rayStart, Vec3.multiply(intersectionInfo.distance, rayDirection));
|
||||||
|
} else {
|
||||||
|
intersectionPoint = planePosition;
|
||||||
|
}
|
||||||
|
intersectionInfo.point = intersectionPoint;
|
||||||
|
intersectionInfo.normal = planeNormal;
|
||||||
|
intersectionInfo.searchRay = {
|
||||||
|
origin: rayStart,
|
||||||
|
direction: rayDirection,
|
||||||
|
length: PICK_MAX_DISTANCE
|
||||||
|
};
|
||||||
|
return intersectionInfo;
|
||||||
|
} else {
|
||||||
|
// entity has been destroyed? or is no longer in cache
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) {
|
||||||
|
var rayDirectionDotPlaneNormal = Vec3.dot(rayDirection, planeNormal);
|
||||||
|
if (rayDirectionDotPlaneNormal > 0.00001 || rayDirectionDotPlaneNormal < -0.00001) {
|
||||||
|
var rayStartDotPlaneNormal = Vec3.dot(Vec3.subtract(planePosition, rayStart), planeNormal);
|
||||||
|
var distance = rayStartDotPlaneNormal / rayDirectionDotPlaneNormal;
|
||||||
|
return {hit: true, distance: distance};
|
||||||
|
} else {
|
||||||
|
// ray is parallel to the plane
|
||||||
|
return {hit: false, distance: 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function stateToName(state) {
|
function stateToName(state) {
|
||||||
return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???";
|
return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???";
|
||||||
|
@ -733,8 +803,6 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.searchSphereOn = function(location, size, color) {
|
this.searchSphereOn = function(location, size, color) {
|
||||||
|
|
||||||
var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP);
|
var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP);
|
||||||
|
@ -968,7 +1036,7 @@ function MyController(hand) {
|
||||||
} else if (potentialEquipHotspot && Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) {
|
} else if (potentialEquipHotspot && Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) {
|
||||||
Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand);
|
Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand);
|
||||||
this.lastHapticPulseLocation = currentLocation;
|
this.lastHapticPulseLocation = currentLocation;
|
||||||
}
|
}
|
||||||
this.prevPotentialEquipHotspot = potentialEquipHotspot;
|
this.prevPotentialEquipHotspot = potentialEquipHotspot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1022,7 +1090,9 @@ function MyController(hand) {
|
||||||
entityID: intersection.entityID,
|
entityID: intersection.entityID,
|
||||||
overlayID: intersection.overlayID,
|
overlayID: intersection.overlayID,
|
||||||
searchRay: pickRay,
|
searchRay: pickRay,
|
||||||
distance: Vec3.distance(pickRay.origin, intersection.intersection)
|
distance: Vec3.distance(pickRay.origin, intersection.intersection),
|
||||||
|
intersection: intersection.intersection,
|
||||||
|
normal: intersection.surfaceNormal
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return result;
|
return result;
|
||||||
|
@ -1245,6 +1315,10 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.searchEnter = function() {
|
||||||
|
mostRecentSearchingHand = this.hand;
|
||||||
|
};
|
||||||
|
|
||||||
this.search = function(deltaTime, timestamp) {
|
this.search = function(deltaTime, timestamp) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var name;
|
var name;
|
||||||
|
@ -1262,6 +1336,12 @@ function MyController(hand) {
|
||||||
|
|
||||||
var handPosition = this.getHandPosition();
|
var handPosition = this.getHandPosition();
|
||||||
|
|
||||||
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
|
|
||||||
|
if (rayPickInfo.entityID) {
|
||||||
|
entityPropertiesCache.addEntity(rayPickInfo.entityID);
|
||||||
|
}
|
||||||
|
|
||||||
var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||||
entityPropertiesCache.addEntities(candidateEntities);
|
entityPropertiesCache.addEntities(candidateEntities);
|
||||||
|
|
||||||
|
@ -1279,10 +1359,8 @@ function MyController(hand) {
|
||||||
return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE);
|
return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE);
|
||||||
});
|
});
|
||||||
|
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
|
||||||
if (rayPickInfo.entityID) {
|
if (rayPickInfo.entityID) {
|
||||||
this.intersectionDistance = rayPickInfo.distance;
|
this.intersectionDistance = rayPickInfo.distance;
|
||||||
entityPropertiesCache.addEntity(rayPickInfo.entityID);
|
|
||||||
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
||||||
grabbableEntities.push(rayPickInfo.entityID);
|
grabbableEntities.push(rayPickInfo.entityID);
|
||||||
}
|
}
|
||||||
|
@ -1331,6 +1409,68 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pointerEvent;
|
||||||
|
if (rayPickInfo.entityID && Entities.wantsHandControllerPointerEvents(rayPickInfo.entityID)) {
|
||||||
|
entity = rayPickInfo.entityID;
|
||||||
|
name = entityPropertiesCache.getProps(entity).name;
|
||||||
|
|
||||||
|
if (Entities.keyboardFocusEntity != entity) {
|
||||||
|
Entities.keyboardFocusEntity = entity;
|
||||||
|
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||||
|
pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection),
|
||||||
|
pos3D: rayPickInfo.intersection,
|
||||||
|
normal: rayPickInfo.normal,
|
||||||
|
direction: rayPickInfo.searchRay.direction,
|
||||||
|
button: "None",
|
||||||
|
isPrimaryButton: false,
|
||||||
|
isSecondaryButton: false,
|
||||||
|
isTertiaryButton: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.hoverEntity = entity;
|
||||||
|
Entities.sendHoverEnterEntity(entity, pointerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send mouse events for button highlights and tooltips.
|
||||||
|
if (this.hand == mostRecentSearchingHand || (this.hand !== mostRecentSearchingHand &&
|
||||||
|
this.getOtherHandController().state !== STATE_SEARCHING &&
|
||||||
|
this.getOtherHandController().state !== STATE_ENTITY_TOUCHING)) {
|
||||||
|
|
||||||
|
// most recently searching hand has priority over other hand, for the purposes of button highlighting.
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||||
|
pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection),
|
||||||
|
pos3D: rayPickInfo.intersection,
|
||||||
|
normal: rayPickInfo.normal,
|
||||||
|
direction: rayPickInfo.searchRay.direction,
|
||||||
|
button: "None",
|
||||||
|
isPrimaryButton: false,
|
||||||
|
isSecondaryButton: false,
|
||||||
|
isTertiaryButton: false
|
||||||
|
};
|
||||||
|
|
||||||
|
Entities.sendMouseMoveOnEntity(entity, pointerEvent);
|
||||||
|
Entities.sendHoverOverEntity(entity, pointerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.triggerSmoothedGrab() && !isEditing()) {
|
||||||
|
this.grabbedEntity = entity;
|
||||||
|
this.setState(STATE_ENTITY_TOUCHING, "begin touching entity '" + name + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (this.hoverEntity) {
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: this.hand + 1
|
||||||
|
};
|
||||||
|
Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent);
|
||||||
|
this.hoverEntity = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (rayPickInfo.entityID) {
|
if (rayPickInfo.entityID) {
|
||||||
entity = rayPickInfo.entityID;
|
entity = rayPickInfo.entityID;
|
||||||
name = entityPropertiesCache.getProps(entity).name;
|
name = entityPropertiesCache.getProps(entity).name;
|
||||||
|
@ -1361,7 +1501,6 @@ function MyController(hand) {
|
||||||
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.searchIndicatorOn(rayPickInfo.searchRay);
|
this.searchIndicatorOn(rayPickInfo.searchRay);
|
||||||
Reticle.setVisible(false);
|
Reticle.setVisible(false);
|
||||||
};
|
};
|
||||||
|
@ -1446,8 +1585,8 @@ function MyController(hand) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.distanceHolding = function(deltaTime, timestamp) {
|
this.distanceHolding = function(deltaTime, timestamp) {
|
||||||
|
|
||||||
if (!this.triggerClicked) {
|
if (!this.triggerClicked) {
|
||||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||||
this.setState(STATE_OFF, "trigger released");
|
this.setState(STATE_OFF, "trigger released");
|
||||||
return;
|
return;
|
||||||
|
@ -1537,9 +1676,9 @@ function MyController(hand) {
|
||||||
|
|
||||||
// visualizations
|
// visualizations
|
||||||
|
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
|
|
||||||
this.overlayLineOn(rayPickInfo.searchRay.origin, grabbedProperties.position, COLORS_GRAB_DISTANCE_HOLD);
|
this.overlayLineOn(rayPickInfo.searchRay.origin, grabbedProperties.position, COLORS_GRAB_DISTANCE_HOLD);
|
||||||
|
|
||||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
||||||
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
|
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||||
|
@ -1970,6 +2109,93 @@ function MyController(hand) {
|
||||||
this.release();
|
this.release();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.entityTouchingEnter = function() {
|
||||||
|
// test for intersection between controller laser and web entity plane.
|
||||||
|
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand);
|
||||||
|
if (intersectInfo) {
|
||||||
|
var pointerEvent = {
|
||||||
|
type: "Press",
|
||||||
|
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||||
|
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
|
||||||
|
pos3D: intersectInfo.point,
|
||||||
|
normal: intersectInfo.normal,
|
||||||
|
direction: intersectInfo.searchRay.direction,
|
||||||
|
button: "Primary",
|
||||||
|
isPrimaryButton: true,
|
||||||
|
isSecondaryButton: false,
|
||||||
|
isTertiaryButton: false
|
||||||
|
};
|
||||||
|
|
||||||
|
Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.entityTouchingExit = function() {
|
||||||
|
// test for intersection between controller laser and web entity plane.
|
||||||
|
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand);
|
||||||
|
if (intersectInfo) {
|
||||||
|
var pointerEvent = {
|
||||||
|
type: "Release",
|
||||||
|
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||||
|
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
|
||||||
|
pos3D: intersectInfo.point,
|
||||||
|
normal: intersectInfo.normal,
|
||||||
|
direction: intersectInfo.searchRay.direction,
|
||||||
|
button: "Primary",
|
||||||
|
isPrimaryButton: true,
|
||||||
|
isSecondaryButton: false,
|
||||||
|
isTertiaryButton: false
|
||||||
|
};
|
||||||
|
|
||||||
|
Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
this.focusedEntity = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.entityTouching = function() {
|
||||||
|
entityPropertiesCache.addEntity(this.grabbedEntity);
|
||||||
|
|
||||||
|
if (!this.triggerSmoothedGrab()) {
|
||||||
|
this.setState(STATE_OFF, "released trigger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for intersection between controller laser and web entity plane.
|
||||||
|
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand);
|
||||||
|
if (intersectInfo) {
|
||||||
|
|
||||||
|
if (Entities.keyboardFocusEntity != this.grabbedEntity) {
|
||||||
|
Entities.keyboardFocusEntity = this.grabbedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||||
|
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
|
||||||
|
pos3D: intersectInfo.point,
|
||||||
|
normal: intersectInfo.normal,
|
||||||
|
direction: intersectInfo.searchRay.direction,
|
||||||
|
button: "NoButtons",
|
||||||
|
isPrimaryButton: true,
|
||||||
|
isSecondaryButton: false,
|
||||||
|
isTertiaryButton: false
|
||||||
|
};
|
||||||
|
|
||||||
|
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
|
|
||||||
|
this.intersectionDistance = intersectInfo.distance;
|
||||||
|
this.searchIndicatorOn(intersectInfo.searchRay);
|
||||||
|
Reticle.setVisible(false);
|
||||||
|
} else {
|
||||||
|
this.setState(STATE_OFF, "grabbed entity was destroyed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.release = function() {
|
this.release = function() {
|
||||||
Messages.sendLocalMessage('Hifi-Teleport-Disabler','none');
|
Messages.sendLocalMessage('Hifi-Teleport-Disabler','none');
|
||||||
this.turnOffVisualizations();
|
this.turnOffVisualizations();
|
||||||
|
|
Loading…
Reference in a new issue