//// Overlays.cpp// interface/src/ui/overlays//// Copyright 2014 High Fidelity, Inc.// Copyright 2022-2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "Overlays.h" #include <limits> #include <shared/QtHelpers.h>#include <OffscreenUi.h>#include <render/Scene.h>#include <RegisteredMetaTypes.h> #include "Application.h"#include "InterfaceLogging.h" #include "ImageOverlay.h"#include "TextOverlay.h"#include "RectangleOverlay.h" #include <raypick/RayPick.h>#include <PointerManager.h>#include <raypick/MouseTransformNode.h>#include <PickManager.h>#include <ScriptEngine.h>#include <ScriptEngineCast.h> #include <RenderableWebEntityItem.h>#include "VariantMapToScriptValue.h" #include "ui/Keyboard.h"#include <QtQuick/QQuickWindow> Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") std::unordered_map<QString, QString> Overlays::_entityToOverlayTypes;std::unordered_map<QString, QString> Overlays::_overlayToEntityTypes; STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ auto scriptEngine = manager->engine().get(); scriptRegisterMetaType<RayToOverlayIntersectionResult, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue>(scriptEngine);})); Overlays::Overlays() { ADD_TYPE_MAP(Box, cube); ADD_TYPE_MAP(Sphere, sphere); _overlayToEntityTypes["rectangle3d"] = "Shape"; ADD_TYPE_MAP(Shape, shape); ADD_TYPE_MAP(Model, model); ADD_TYPE_MAP(Text, text3d); ADD_TYPE_MAP(Image, image3d); _overlayToEntityTypes["billboard"] = "Image"; ADD_TYPE_MAP(Web, web3d); ADD_TYPE_MAP(PolyLine, line3d); ADD_TYPE_MAP(Grid, grid); ADD_TYPE_MAP(Gizmo, circle3d);} void Overlays::cleanupAllOverlays() { _shuttingDown = true; QMap<QUuid, Overlay::Pointer> overlays; { QMutexLocker locker(&_mutex); overlays.swap(_overlays); } foreach(Overlay::Pointer overlay, overlays) { _overlaysToDelete.push_back(overlay); } cleanupOverlaysToDelete();} void Overlays::init() { auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data(); auto pointerManager = DependencyManager::get<PointerManager>().data(); connect(pointerManager, &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); connect(pointerManager, &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); connect(pointerManager, &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); connect(pointerManager, &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); connect(pointerManager, &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); connect(pointerManager, &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); connect(pointerManager, &PointerManager::scrollOverlay, entityScriptingInterface, &EntityScriptingInterface::scrollOnEntity); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &Overlays::mousePressOnPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOffEntity, this, &Overlays::mousePressOffPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOnEntity, this, &Overlays::mouseDoublePressOnPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOffEntity, this, &Overlays::mouseDoublePressOffPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &Overlays::mouseReleasePointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, &Overlays::mouseMovePointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity , this, &Overlays::hoverEnterPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, &Overlays::hoverOverPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &Overlays::hoverLeavePointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::scrollOnEntity, this, &Overlays::scrollPointerEvent);} void Overlays::update(float deltatime) { cleanupOverlaysToDelete();} void Overlays::cleanupOverlaysToDelete() { if (!_overlaysToDelete.isEmpty()) { render::ScenePointer scene = qApp->getMain3DScene(); render::Transaction transaction; { do { Overlay::Pointer overlay = _overlaysToDelete.takeLast(); auto itemID = overlay->getRenderItemID(); if (render::Item::isValidID(itemID)) { overlay->removeFromScene(overlay, scene, transaction); } } while (!_overlaysToDelete.isEmpty()); } if (transaction.hasRemovedItems()) { scene->enqueueTransaction(transaction); } }} void Overlays::render(RenderArgs* renderArgs) { PROFILE_RANGE(render_overlays, __FUNCTION__); gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get<GeometryCache>(); auto textureCache = DependencyManager::get<TextureCache>(); auto size = glm::uvec2(qApp->getUiSize()); int width = size.x; int height = size.y; mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, -1000, 1000); QMutexLocker locker(&_mutex); foreach(Overlay::Pointer thisOverlay, _overlays) { // Reset all batch pipeline settings between overlay geometryCache->useSimpleDrawPipeline(batch); batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? batch.setProjectionTransform(legacyProjection); batch.setModelTransform(Transform()); batch.resetViewTransform(); thisOverlay->render(renderArgs); }} Overlay::Pointer Overlays::take2DOverlay(const QUuid& id) { if (_shuttingDown) { return nullptr; } QMutexLocker locker(&_mutex); auto overlayIter = _overlays.find(id); if (overlayIter != _overlays.end()) { return _overlays.take(id); } return nullptr;} Overlay::Pointer Overlays::get2DOverlay(const QUuid& id) const { if (_shuttingDown) { return nullptr; } QMutexLocker locker(&_mutex); auto overlayIter = _overlays.find(id); if (overlayIter != _overlays.end()) { return overlayIter.value(); } return nullptr;} QString Overlays::entityToOverlayType(const QString& type) { auto iter = _entityToOverlayTypes.find(type); if (iter != _entityToOverlayTypes.end()) { return iter->second; } return "unknown";} static QHash<QUuid, std::pair<glm::quat, bool>> savedRotations = QHash<QUuid, std::pair<glm::quat, bool>>(); QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } if (type == ImageOverlay::TYPE || type == TextOverlay::TYPE || type == RectangleOverlay::TYPE) {#if !defined(DISABLE_QML) if (QThread::currentThread() != thread()) { QUuid result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QString&, type), Q_ARG(const QVariant&, properties)); return result; } Overlay::Pointer overlay; if (type == ImageOverlay::TYPE) { overlay = std::shared_ptr<ImageOverlay>(new ImageOverlay(), [](ImageOverlay* ptr) { ptr->deleteLater(); }); } else if (type == TextOverlay::TYPE) { overlay = std::shared_ptr<TextOverlay>(new TextOverlay(), [](TextOverlay* ptr) { ptr->deleteLater(); }); } else if (type == RectangleOverlay::TYPE) { overlay = std::shared_ptr<RectangleOverlay>(new RectangleOverlay(), [](RectangleOverlay* ptr) { ptr->deleteLater(); }); } if (overlay) { overlay->setProperties(properties.toMap()); return add2DOverlay(overlay); }#endif return QUuid(); } return UNKNOWN_ENTITY_ID;} QUuid Overlays::add2DOverlay(const Overlay::Pointer& overlay) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } QUuid thisID = QUuid::createUuid(); overlay->setID(thisID); overlay->setStackOrder(_stackOrder++); { QMutexLocker locker(&_mutex); _overlays[thisID] = overlay; } return thisID;} QUuid Overlays::cloneOverlay(const QUuid& id) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QUuid result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QUuid&, id)); return result; } return add2DOverlay(Overlay::Pointer(overlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); } return DependencyManager::get<EntityScriptingInterface>()->cloneEntity(id);} bool Overlays::editOverlay(const QUuid& id, const QVariant& properties) { if (_shuttingDown) { return false; } auto overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { // NOTE editOverlay can be called very frequently in scripts and can't afford to // block waiting on the main thread. Additionally, no script actually // examines the return value and does something useful with it, so use a non-blocking // invoke and just always return true QMetaObject::invokeMethod(this, "editOverlay", Q_ARG(const QUuid&, id), Q_ARG(const QVariant&, properties)); return true; } overlay->setProperties(properties.toMap()); return true; } return false;} bool Overlays::editOverlays(const QVariant& propertiesById) { if (_shuttingDown) { return false; } bool deferOverlays = QThread::currentThread() != thread(); QVariantMap deferred; const QVariantMap map = propertiesById.toMap(); for (const auto& key : map.keys()) { QUuid id = QUuid(key); const QVariant& properties = map[key]; Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (deferOverlays) { deferred[key] = properties; continue; } overlay->setProperties(properties.toMap()); } else { qDebug() << "Overlays::editOverlays doesn't support editing entities anymore"; } } // For 2D/QML overlays, we need to perform the edit on the main thread if (!deferred.empty()) { // NOTE see comment on editOverlay for why this is not a blocking call QMetaObject::invokeMethod(this, "editOverlays", Q_ARG(const QVariant&, deferred)); } return true;} void Overlays::deleteOverlay(const QUuid& id) { if (_shuttingDown) { return; } Overlay::Pointer overlay = take2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "deleteOverlay", Q_ARG(const QUuid&, id)); return; } _overlaysToDelete.push_back(overlay); emit overlayDeleted(id); return; } DependencyManager::get<EntityScriptingInterface>()->deleteEntity(id); emit overlayDeleted(id);} QString Overlays::getOverlayType(const QUuid& id) { if (_shuttingDown) { return ""; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QString result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(const QUuid&, id)); return result; } return overlay->getType(); } return entityToOverlayType(DependencyManager::get<EntityScriptingInterface>()->getEntityType(id));} QObject* Overlays::getOverlayObject(const QUuid& id) { Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QObject* result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(const QUuid&, id)); return result; } return qobject_cast<QObject*>(&(*overlay)); } return DependencyManager::get<EntityScriptingInterface>()->getEntityObject(id);} QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } if (QThread::currentThread() != thread()) { QUuid result; BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_RETURN_ARG(QUuid, result), Q_ARG(const glm::vec2&, point)); return result; } QMutexLocker locker(&_mutex); QMapIterator<QUuid, Overlay::Pointer> i(_overlays); unsigned int bestStackOrder = 0; QUuid bestID = UNKNOWN_ENTITY_ID; while (i.hasNext()) { i.next(); auto thisOverlay = std::dynamic_pointer_cast<Overlay2D>(i.value()); if (thisOverlay && thisOverlay->getVisible() && thisOverlay->getBoundingRect().contains(point.x, point.y, false)) { if (thisOverlay->getStackOrder() > bestStackOrder) { bestID = i.key(); bestStackOrder = thisOverlay->getStackOrder(); } } } return bestID;} RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, bool precisionPicking, const ScriptValue& overlayIDsToInclude, const ScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { const QVector<EntityItemID> include = qVectorEntityItemIDFromScriptValue(overlayIDsToInclude); const QVector<EntityItemID> discard = qVectorEntityItemIDFromScriptValue(overlayIDsToDiscard); return findRayIntersectionVector(ray, precisionPicking, include, discard, visibleOnly, collidableOnly);} RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, const QVector<EntityItemID>& include, const QVector<EntityItemID>& discard, bool visibleOnly, bool collidableOnly) { unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); if (!precisionPicking) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } if (visibleOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } if (collidableOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } auto result = DependencyManager::get<EntityScriptingInterface>()->evalRayIntersectionVector(ray, PickFilter(searchFilter), include, discard); RayToOverlayIntersectionResult overlayResult; overlayResult.overlayID = result.entityID; overlayResult.intersects = result.intersects; overlayResult.intersection = result.intersection; overlayResult.distance = result.distance; overlayResult.surfaceNormal = result.surfaceNormal; overlayResult.face = result.face; overlayResult.extraInfo = result.extraInfo; return overlayResult;} ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, const QVector<EntityItemID>& include, const QVector<EntityItemID>& discard, bool visibleOnly, bool collidableOnly) { unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); if (!precisionPicking) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } if (visibleOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } if (collidableOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } auto result = DependencyManager::get<EntityScriptingInterface>()->evalParabolaIntersectionVector(parabola, PickFilter(searchFilter), include, discard); ParabolaToOverlayIntersectionResult overlayResult; overlayResult.overlayID = result.entityID; overlayResult.intersects = result.intersects; overlayResult.intersection = result.intersection; overlayResult.parabolicDistance = result.parabolicDistance; overlayResult.surfaceNormal = result.surfaceNormal; overlayResult.face = result.face; overlayResult.extraInfo = result.extraInfo; return overlayResult;} ScriptValue RayToOverlayIntersectionResultToScriptValue(ScriptEngine* engine, const RayToOverlayIntersectionResult& value) { ScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); ScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); obj.setProperty("overlayID", overlayIDValue); obj.setProperty("distance", value.distance); obj.setProperty("face", boxFaceToString(value.face)); ScriptValue intersection = vec3ToScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); ScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj;} bool RayToOverlayIntersectionResultFromScriptValue(const ScriptValue& object, RayToOverlayIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); ScriptValue overlayIDValue = object.property("overlayID"); quuidFromScriptValue(overlayIDValue, value.overlayID); value.distance = object.property("distance").toVariant().toFloat(); value.face = boxFaceFromString(object.property("face").toVariant().toString()); ScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } ScriptValue surfaceNormal = object.property("surfaceNormal");Lines 1–500 of 806. Load more
// // Overlays.cpp // interface/src/ui/overlays // // Copyright 2014 High Fidelity, Inc. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "Overlays.h" #include <limits> #include <shared/QtHelpers.h> #include <OffscreenUi.h> #include <render/Scene.h> #include <RegisteredMetaTypes.h> #include "Application.h" #include "InterfaceLogging.h" #include "ImageOverlay.h" #include "TextOverlay.h" #include "RectangleOverlay.h" #include <raypick/RayPick.h> #include <PointerManager.h> #include <raypick/MouseTransformNode.h> #include <PickManager.h> #include <ScriptEngine.h> #include <ScriptEngineCast.h> #include <RenderableWebEntityItem.h> #include "VariantMapToScriptValue.h" #include "ui/Keyboard.h" #include <QtQuick/QQuickWindow> Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") std::unordered_map<QString, QString> Overlays::_entityToOverlayTypes; std::unordered_map<QString, QString> Overlays::_overlayToEntityTypes; STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ auto scriptEngine = manager->engine().get(); scriptRegisterMetaType<RayToOverlayIntersectionResult, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue>(scriptEngine); })); Overlays::Overlays() { ADD_TYPE_MAP(Box, cube); ADD_TYPE_MAP(Sphere, sphere); _overlayToEntityTypes["rectangle3d"] = "Shape"; ADD_TYPE_MAP(Shape, shape); ADD_TYPE_MAP(Model, model); ADD_TYPE_MAP(Text, text3d); ADD_TYPE_MAP(Image, image3d); _overlayToEntityTypes["billboard"] = "Image"; ADD_TYPE_MAP(Web, web3d); ADD_TYPE_MAP(PolyLine, line3d); ADD_TYPE_MAP(Grid, grid); ADD_TYPE_MAP(Gizmo, circle3d); } void Overlays::cleanupAllOverlays() { _shuttingDown = true; QMap<QUuid, Overlay::Pointer> overlays; { QMutexLocker locker(&_mutex); overlays.swap(_overlays); } foreach(Overlay::Pointer overlay, overlays) { _overlaysToDelete.push_back(overlay); } cleanupOverlaysToDelete(); } void Overlays::init() { auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data(); auto pointerManager = DependencyManager::get<PointerManager>().data(); connect(pointerManager, &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); connect(pointerManager, &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); connect(pointerManager, &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); connect(pointerManager, &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); connect(pointerManager, &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); connect(pointerManager, &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); connect(pointerManager, &PointerManager::scrollOverlay, entityScriptingInterface, &EntityScriptingInterface::scrollOnEntity); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &Overlays::mousePressOnPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOffEntity, this, &Overlays::mousePressOffPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOnEntity, this, &Overlays::mouseDoublePressOnPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOffEntity, this, &Overlays::mouseDoublePressOffPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &Overlays::mouseReleasePointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, &Overlays::mouseMovePointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity , this, &Overlays::hoverEnterPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, &Overlays::hoverOverPointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &Overlays::hoverLeavePointerEvent); connect(entityScriptingInterface, &EntityScriptingInterface::scrollOnEntity, this, &Overlays::scrollPointerEvent); } void Overlays::update(float deltatime) { cleanupOverlaysToDelete(); } void Overlays::cleanupOverlaysToDelete() { if (!_overlaysToDelete.isEmpty()) { render::ScenePointer scene = qApp->getMain3DScene(); render::Transaction transaction; { do { Overlay::Pointer overlay = _overlaysToDelete.takeLast(); auto itemID = overlay->getRenderItemID(); if (render::Item::isValidID(itemID)) { overlay->removeFromScene(overlay, scene, transaction); } } while (!_overlaysToDelete.isEmpty()); } if (transaction.hasRemovedItems()) { scene->enqueueTransaction(transaction); } } } void Overlays::render(RenderArgs* renderArgs) { PROFILE_RANGE(render_overlays, __FUNCTION__); gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get<GeometryCache>(); auto textureCache = DependencyManager::get<TextureCache>(); auto size = glm::uvec2(qApp->getUiSize()); int width = size.x; int height = size.y; mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, -1000, 1000); QMutexLocker locker(&_mutex); foreach(Overlay::Pointer thisOverlay, _overlays) { // Reset all batch pipeline settings between overlay geometryCache->useSimpleDrawPipeline(batch); batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? batch.setProjectionTransform(legacyProjection); batch.setModelTransform(Transform()); batch.resetViewTransform(); thisOverlay->render(renderArgs); } } Overlay::Pointer Overlays::take2DOverlay(const QUuid& id) { if (_shuttingDown) { return nullptr; } QMutexLocker locker(&_mutex); auto overlayIter = _overlays.find(id); if (overlayIter != _overlays.end()) { return _overlays.take(id); } return nullptr; } Overlay::Pointer Overlays::get2DOverlay(const QUuid& id) const { if (_shuttingDown) { return nullptr; } QMutexLocker locker(&_mutex); auto overlayIter = _overlays.find(id); if (overlayIter != _overlays.end()) { return overlayIter.value(); } return nullptr; } QString Overlays::entityToOverlayType(const QString& type) { auto iter = _entityToOverlayTypes.find(type); if (iter != _entityToOverlayTypes.end()) { return iter->second; } return "unknown"; } static QHash<QUuid, std::pair<glm::quat, bool>> savedRotations = QHash<QUuid, std::pair<glm::quat, bool>>(); QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } if (type == ImageOverlay::TYPE || type == TextOverlay::TYPE || type == RectangleOverlay::TYPE) { #if !defined(DISABLE_QML) if (QThread::currentThread() != thread()) { QUuid result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QString&, type), Q_ARG(const QVariant&, properties)); return result; } Overlay::Pointer overlay; if (type == ImageOverlay::TYPE) { overlay = std::shared_ptr<ImageOverlay>(new ImageOverlay(), [](ImageOverlay* ptr) { ptr->deleteLater(); }); } else if (type == TextOverlay::TYPE) { overlay = std::shared_ptr<TextOverlay>(new TextOverlay(), [](TextOverlay* ptr) { ptr->deleteLater(); }); } else if (type == RectangleOverlay::TYPE) { overlay = std::shared_ptr<RectangleOverlay>(new RectangleOverlay(), [](RectangleOverlay* ptr) { ptr->deleteLater(); }); } if (overlay) { overlay->setProperties(properties.toMap()); return add2DOverlay(overlay); } #endif return QUuid(); } return UNKNOWN_ENTITY_ID; } QUuid Overlays::add2DOverlay(const Overlay::Pointer& overlay) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } QUuid thisID = QUuid::createUuid(); overlay->setID(thisID); overlay->setStackOrder(_stackOrder++); { QMutexLocker locker(&_mutex); _overlays[thisID] = overlay; } return thisID; } QUuid Overlays::cloneOverlay(const QUuid& id) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QUuid result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QUuid&, id)); return result; } return add2DOverlay(Overlay::Pointer(overlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); } return DependencyManager::get<EntityScriptingInterface>()->cloneEntity(id); } bool Overlays::editOverlay(const QUuid& id, const QVariant& properties) { if (_shuttingDown) { return false; } auto overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { // NOTE editOverlay can be called very frequently in scripts and can't afford to // block waiting on the main thread. Additionally, no script actually // examines the return value and does something useful with it, so use a non-blocking // invoke and just always return true QMetaObject::invokeMethod(this, "editOverlay", Q_ARG(const QUuid&, id), Q_ARG(const QVariant&, properties)); return true; } overlay->setProperties(properties.toMap()); return true; } return false; } bool Overlays::editOverlays(const QVariant& propertiesById) { if (_shuttingDown) { return false; } bool deferOverlays = QThread::currentThread() != thread(); QVariantMap deferred; const QVariantMap map = propertiesById.toMap(); for (const auto& key : map.keys()) { QUuid id = QUuid(key); const QVariant& properties = map[key]; Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (deferOverlays) { deferred[key] = properties; continue; } overlay->setProperties(properties.toMap()); } else { qDebug() << "Overlays::editOverlays doesn't support editing entities anymore"; } } // For 2D/QML overlays, we need to perform the edit on the main thread if (!deferred.empty()) { // NOTE see comment on editOverlay for why this is not a blocking call QMetaObject::invokeMethod(this, "editOverlays", Q_ARG(const QVariant&, deferred)); } return true; } void Overlays::deleteOverlay(const QUuid& id) { if (_shuttingDown) { return; } Overlay::Pointer overlay = take2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "deleteOverlay", Q_ARG(const QUuid&, id)); return; } _overlaysToDelete.push_back(overlay); emit overlayDeleted(id); return; } DependencyManager::get<EntityScriptingInterface>()->deleteEntity(id); emit overlayDeleted(id); } QString Overlays::getOverlayType(const QUuid& id) { if (_shuttingDown) { return ""; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QString result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(const QUuid&, id)); return result; } return overlay->getType(); } return entityToOverlayType(DependencyManager::get<EntityScriptingInterface>()->getEntityType(id)); } QObject* Overlays::getOverlayObject(const QUuid& id) { Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (QThread::currentThread() != thread()) { QObject* result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(const QUuid&, id)); return result; } return qobject_cast<QObject*>(&(*overlay)); } return DependencyManager::get<EntityScriptingInterface>()->getEntityObject(id); } QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } if (QThread::currentThread() != thread()) { QUuid result; BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_RETURN_ARG(QUuid, result), Q_ARG(const glm::vec2&, point)); return result; } QMutexLocker locker(&_mutex); QMapIterator<QUuid, Overlay::Pointer> i(_overlays); unsigned int bestStackOrder = 0; QUuid bestID = UNKNOWN_ENTITY_ID; while (i.hasNext()) { i.next(); auto thisOverlay = std::dynamic_pointer_cast<Overlay2D>(i.value()); if (thisOverlay && thisOverlay->getVisible() && thisOverlay->getBoundingRect().contains(point.x, point.y, false)) { if (thisOverlay->getStackOrder() > bestStackOrder) { bestID = i.key(); bestStackOrder = thisOverlay->getStackOrder(); } } } return bestID; } RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, bool precisionPicking, const ScriptValue& overlayIDsToInclude, const ScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { const QVector<EntityItemID> include = qVectorEntityItemIDFromScriptValue(overlayIDsToInclude); const QVector<EntityItemID> discard = qVectorEntityItemIDFromScriptValue(overlayIDsToDiscard); return findRayIntersectionVector(ray, precisionPicking, include, discard, visibleOnly, collidableOnly); } RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, const QVector<EntityItemID>& include, const QVector<EntityItemID>& discard, bool visibleOnly, bool collidableOnly) { unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); if (!precisionPicking) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } if (visibleOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } if (collidableOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } auto result = DependencyManager::get<EntityScriptingInterface>()->evalRayIntersectionVector(ray, PickFilter(searchFilter), include, discard); RayToOverlayIntersectionResult overlayResult; overlayResult.overlayID = result.entityID; overlayResult.intersects = result.intersects; overlayResult.intersection = result.intersection; overlayResult.distance = result.distance; overlayResult.surfaceNormal = result.surfaceNormal; overlayResult.face = result.face; overlayResult.extraInfo = result.extraInfo; return overlayResult; } ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, const QVector<EntityItemID>& include, const QVector<EntityItemID>& discard, bool visibleOnly, bool collidableOnly) { unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); if (!precisionPicking) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } if (visibleOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } if (collidableOnly) { searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } auto result = DependencyManager::get<EntityScriptingInterface>()->evalParabolaIntersectionVector(parabola, PickFilter(searchFilter), include, discard); ParabolaToOverlayIntersectionResult overlayResult; overlayResult.overlayID = result.entityID; overlayResult.intersects = result.intersects; overlayResult.intersection = result.intersection; overlayResult.parabolicDistance = result.parabolicDistance; overlayResult.surfaceNormal = result.surfaceNormal; overlayResult.face = result.face; overlayResult.extraInfo = result.extraInfo; return overlayResult; } ScriptValue RayToOverlayIntersectionResultToScriptValue(ScriptEngine* engine, const RayToOverlayIntersectionResult& value) { ScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); ScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); obj.setProperty("overlayID", overlayIDValue); obj.setProperty("distance", value.distance); obj.setProperty("face", boxFaceToString(value.face)); ScriptValue intersection = vec3ToScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); ScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } bool RayToOverlayIntersectionResultFromScriptValue(const ScriptValue& object, RayToOverlayIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); ScriptValue overlayIDValue = object.property("overlayID"); quuidFromScriptValue(overlayIDValue, value.overlayID); value.distance = object.property("distance").toVariant().toFloat(); value.face = boxFaceFromString(object.property("face").toVariant().toString()); ScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } ScriptValue surfaceNormal = object.property("surfaceNormal");