// // Overlays.cpp // interface/src/ui/overlays // // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "Overlays.h" #include #include #include #include #include #include #include "Application.h" #include "InterfaceLogging.h" #include "ImageOverlay.h" #include "TextOverlay.h" #include "RectangleOverlay.h" #include #include #include #include #include #include "VariantMapToScriptValue.h" #include "ui/Keyboard.h" #include Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") std::unordered_map Overlays::_entityToOverlayTypes; std::unordered_map Overlays::_overlayToEntityTypes; Overlays::Overlays() { auto pointerManager = DependencyManager::get(); connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent); connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, this, &Overlays::hoverOverPointerEvent); connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Overlays::hoverLeavePointerEvent); connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressPointerEvent); connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Overlays::mouseMovePointerEvent); connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Overlays::mouseReleasePointerEvent); 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 overlays; { QMutexLocker locker(&_mutex); overlays.swap(_overlays); } foreach(Overlay::Pointer overlay, overlays) { _overlaysToDelete.push_back(overlay); } cleanupOverlaysToDelete(); } void Overlays::init() { auto entityScriptingInterface = DependencyManager::get().data(); auto pointerManager = DependencyManager::get(); connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); connect(pointerManager.data(), &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); connect(pointerManager.data(), &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); } 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(); auto textureCache = DependencyManager::get(); auto size = glm::uvec2(qApp->getUiSize()); int width = size.x; int height = size.y; mat4 legacyProjection = glm::ortho(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); } } void Overlays::disable() { _enabled = false; } void Overlays::enable() { _enabled = true; } 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"; } QString Overlays::overlayToEntityType(const QString& type) { auto iter = _overlayToEntityTypes.find(type); if (iter != _overlayToEntityTypes.end()) { return iter->second; } return "Unknown"; } #define SET_OVERLAY_PROP_DEFAULT(o, d) \ { \ if (add && !overlayProps.contains(#o)) { \ overlayProps[#o] = d; \ } \ } #define RENAME_PROP(o, e) \ { \ auto iter = overlayProps.find(#o); \ if (iter != overlayProps.end()) { \ overlayProps[#e] = iter.value(); \ } \ } #define RENAME_PROP_CONVERT(o, e, C) \ { \ auto iter = overlayProps.find(#o); \ if (iter != overlayProps.end()) { \ overlayProps[#e] = C(iter.value()); \ } \ } #define OVERLAY_TO_GROUP_ENTITY_PROP(o, g, e) \ { \ auto iter = overlayProps.find(#o); \ if (iter != overlayProps.end()) { \ if (!overlayProps.contains(#g)) { \ overlayProps[#g] = QVariantMap(); \ } \ auto map = overlayProps[#g].toMap(); \ map[#e] = iter.value(); \ overlayProps[#g] = map; \ } \ } #define OVERLAY_TO_GROUP_ENTITY_PROP_DEFAULT(o, g, e, d) \ { \ auto iter = overlayProps.find(#o); \ if (iter != overlayProps.end()) { \ if (!overlayProps.contains(#g)) { \ overlayProps[#g] = QVariantMap(); \ } \ auto map = overlayProps[#g].toMap(); \ map[#e] = iter.value(); \ overlayProps[#g] = map; \ } else if (add) { \ if (!overlayProps.contains(#g)) { \ overlayProps[#g] = QVariantMap(); \ } \ auto map = overlayProps[#g].toMap(); \ map[#e] = d; \ overlayProps[#g] = map; \ } \ } #define OVERLAY_TO_ENTITY_PROP_CONVERT_DEFAULT(o, e, d, C) \ { \ auto iter = overlayProps.find(#o); \ if (iter != overlayProps.end()) { \ overlayProps[#e] = C(iter.value()); \ } else if (add) { \ overlayProps[#e] = C(d); \ } \ } #define OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(o, g, e, C) \ { \ auto iter = overlayProps.find(#o); \ if (iter != overlayProps.end()) { \ if (!overlayProps.contains(#g)) { \ overlayProps[#g] = QVariantMap(); \ } \ auto map = overlayProps[#g].toMap(); \ map[#e] = C(iter.value()); \ overlayProps[#g] = map; \ } \ } #define GROUP_ENTITY_TO_OVERLAY_PROP(g, e, o) \ { \ auto iter = overlayProps.find(#g); \ if (iter != overlayProps.end()) { \ auto map = iter.value().toMap(); \ auto iter2 = map.find(#e); \ if (iter2 != map.end()) { \ overlayProps[#o] = iter2.value(); \ } \ } \ } #define GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(g, e, o, C) \ { \ auto iter = overlayProps.find(#g); \ if (iter != overlayProps.end()) { \ auto map = iter.value().toMap(); \ auto iter2 = map.find(#e); \ if (iter2 != map.end()) { \ overlayProps[#o] = C(iter2.value()); \ } \ } \ } static QHash savedRotations = QHash(); EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id) { glm::quat rotation; return convertOverlayToEntityProperties(overlayProps, rotation, type, add, id); } EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, glm::quat& rotationToSave, const QString& type, bool add, const QUuid& id) { overlayProps["type"] = type; SET_OVERLAY_PROP_DEFAULT(alpha, 0.7); if (type != "PolyLine") { RENAME_PROP(p1, position); RENAME_PROP(start, position); } RENAME_PROP(point, position); if (type != "Model") { RENAME_PROP(scale, dimensions); } else { RENAME_PROP(scale, modelScale); } RENAME_PROP(size, dimensions); RENAME_PROP(orientation, rotation); RENAME_PROP(localOrientation, localRotation); RENAME_PROP(ignoreRayIntersection, ignorePickIntersection); RENAME_PROP_CONVERT(drawInFront, renderLayer, [](const QVariant& v) { return v.toBool() ? "front" : "world"; }); RENAME_PROP_CONVERT(drawHUDLayer, renderLayer, [=](const QVariant& v) { bool f = v.toBool(); if (f) { return QVariant("hud"); } else if (overlayProps.contains("renderLayer")) { return overlayProps["renderLayer"]; } return QVariant("world"); }); OVERLAY_TO_GROUP_ENTITY_PROP_DEFAULT(grabbable, grab, grabbable, false); OVERLAY_TO_GROUP_ENTITY_PROP(pulseMin, pulse, min); OVERLAY_TO_GROUP_ENTITY_PROP(pulseMax, pulse, max); OVERLAY_TO_GROUP_ENTITY_PROP(pulsePeriod, pulse, period); OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(colorPulse, pulse, colorMode, [](const QVariant& v) { float f = v.toFloat(); if (f > 0.0f) { return "in"; } else if (f < 0.0f) { return "out"; } return "none"; }); OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(alphaPulse, pulse, alphaMode, [](const QVariant& v) { float f = v.toFloat(); if (f > 0.0f) { return "in"; } else if (f < 0.0f) { return "out"; } return "none"; }); RENAME_PROP_CONVERT(textures, textures, [](const QVariant& v) { auto map = v.toMap(); if (!map.isEmpty()) { auto json = QJsonDocument::fromVariant(map); if (!json.isNull()) { return QVariant(QString(json.toJson())); } } return v; }); if (type == "Shape" || type == "Box" || type == "Sphere" || type == "Gizmo") { RENAME_PROP(solid, isSolid); RENAME_PROP(isFilled, isSolid); RENAME_PROP(filled, isSolid); OVERLAY_TO_ENTITY_PROP_CONVERT_DEFAULT(isSolid, primitiveMode, false, [](const QVariant& v) { return v.toBool() ? "solid" : "lines"; }); RENAME_PROP(wire, isWire); RENAME_PROP_CONVERT(isWire, primitiveMode, [](const QVariant& v) { return v.toBool() ? "lines" : "solid"; }); } if (type == "Shape") { SET_OVERLAY_PROP_DEFAULT(shape, "Hexagon"); } else if (type == "Model") { RENAME_PROP(url, modelURL); RENAME_PROP(animationSettings, animation); } else if (type == "Image") { RENAME_PROP(url, imageURL); } else if (type == "Text") { RENAME_PROP(color, textColor); } else if (type == "Web") { RENAME_PROP(url, sourceUrl); RENAME_PROP_CONVERT(inputMode, inputMode, [](const QVariant& v) { return v.toString() == "Mouse" ? "mouse" : "touch"; }); } else if (type == "Gizmo") { RENAME_PROP(radius, outerRadius); if (add || overlayProps.contains("outerRadius")) { float ratio = 2.0f; { auto iter = overlayProps.find("outerRadius"); if (iter != overlayProps.end()) { ratio = iter.value().toFloat() / 0.5f; } } glm::vec3 dimensions = glm::vec3(1.0f); { auto iter = overlayProps.find("dimensions"); if (iter != overlayProps.end()) { dimensions = vec3FromVariant(iter.value()); } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_DIMENSIONS; dimensions = DependencyManager::get()->getEntityProperties(id, desiredProperties).getDimensions(); } } overlayProps["dimensions"] = vec3toVariant(ratio * dimensions); } if (add || overlayProps.contains("rotation")) { glm::quat rotation; { auto iter = overlayProps.find("rotation"); if (iter != overlayProps.end()) { rotation = quatFromVariant(iter.value()); } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_ROTATION; rotation = DependencyManager::get()->getEntityProperties(id, desiredProperties).getRotation(); } } overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); } if (add || overlayProps.contains("localRotation")) { glm::quat rotation; { auto iter = overlayProps.find("localRotation"); if (iter != overlayProps.end()) { rotation = quatFromVariant(iter.value()); } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_LOCAL_ROTATION; rotation = DependencyManager::get()->getEntityProperties(id, desiredProperties).getLocalRotation(); } } overlayProps["localRotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); } { RENAME_PROP(color, innerStartColor); RENAME_PROP(color, innerEndColor); RENAME_PROP(color, outerStartColor); RENAME_PROP(color, outerEndColor); RENAME_PROP(startColor, innerStartColor); RENAME_PROP(startColor, outerStartColor); RENAME_PROP(endColor, innerEndColor); RENAME_PROP(endColor, outerEndColor); RENAME_PROP(innerColor, innerStartColor); RENAME_PROP(innerColor, innerEndColor); RENAME_PROP(outerColor, outerStartColor); RENAME_PROP(outerColor, outerEndColor); } { RENAME_PROP(alpha, innerStartAlpha); RENAME_PROP(alpha, innerEndAlpha); RENAME_PROP(alpha, outerStartAlpha); RENAME_PROP(alpha, outerEndAlpha); RENAME_PROP(startAlpha, innerStartAlpha); RENAME_PROP(startAlpha, outerStartAlpha); RENAME_PROP(endAlpha, innerEndAlpha); RENAME_PROP(endAlpha, outerEndAlpha); RENAME_PROP(innerAlpha, innerStartAlpha); RENAME_PROP(innerAlpha, innerEndAlpha); RENAME_PROP(outerAlpha, outerStartAlpha); RENAME_PROP(outerAlpha, outerEndAlpha); } OVERLAY_TO_GROUP_ENTITY_PROP(startAt, ring, startAngle); OVERLAY_TO_GROUP_ENTITY_PROP(endAt, ring, endAngle); OVERLAY_TO_GROUP_ENTITY_PROP(innerRadius, ring, innerRadius); OVERLAY_TO_GROUP_ENTITY_PROP(innerStartColor, ring, innerStartColor); OVERLAY_TO_GROUP_ENTITY_PROP(innerEndColor, ring, innerEndColor); OVERLAY_TO_GROUP_ENTITY_PROP(outerStartColor, ring, outerStartColor); OVERLAY_TO_GROUP_ENTITY_PROP(outerEndColor, ring, outerEndColor); OVERLAY_TO_GROUP_ENTITY_PROP(innerStartAlpha, ring, innerStartAlpha); OVERLAY_TO_GROUP_ENTITY_PROP(innerEndAlpha, ring, innerEndAlpha); OVERLAY_TO_GROUP_ENTITY_PROP(outerStartAlpha, ring, outerStartAlpha); OVERLAY_TO_GROUP_ENTITY_PROP(outerEndAlpha, ring, outerEndAlpha); OVERLAY_TO_GROUP_ENTITY_PROP(hasTickMarks, ring, hasTickMarks); OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksAngle, ring, majorTickMarksAngle); OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksAngle, ring, minorTickMarksAngle); OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksLength, ring, majorTickMarksLength); OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksLength, ring, minorTickMarksLength); OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksColor, ring, majorTickMarksColor); OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksColor, ring, minorTickMarksColor); } else if (type == "PolyLine") { RENAME_PROP(startPoint, p1); RENAME_PROP(start, p1); RENAME_PROP(endPoint, p2); RENAME_PROP(end, p2); RENAME_PROP(p1, position); RENAME_PROP_CONVERT(p1, p1, [](const QVariant& v) { return vec3toVariant(glm::vec3(0.0f)); }); RENAME_PROP_CONVERT(p2, p2, [=](const QVariant& v) { glm::vec3 position; auto iter2 = overlayProps.find("position"); if (iter2 != overlayProps.end()) { position = vec3FromVariant(iter2.value()); } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_POSITION; position = DependencyManager::get()->getEntityProperties(id, desiredProperties).getPosition(); } return vec3toVariant(vec3FromVariant(v) - position); }); RENAME_PROP(localStart, p1); RENAME_PROP(localEnd, p2); { QVariantList points; { auto iter = overlayProps.find("p1"); if (iter != overlayProps.end()) { points.push_back(iter.value()); } } { auto iter = overlayProps.find("p2"); if (iter != overlayProps.end()) { points.push_back(iter.value()); } } overlayProps["linePoints"] = points; } { auto iter = overlayProps.find("lineWidth"); if (iter != overlayProps.end()) { QVariantList widths; QVariant width = iter.value(); widths.append(width); widths.append(width); overlayProps["strokeWidths"] = widths; } } RENAME_PROP_CONVERT(glow, glow, [](const QVariant& v) { return v.toFloat() > 0.0f ? true : false; }); SET_OVERLAY_PROP_DEFAULT(faceCamera, true); { QVariantList normals; normals.append(vec3toVariant(Vectors::UP)); normals.append(vec3toVariant(Vectors::UP)); SET_OVERLAY_PROP_DEFAULT(normals, normals); } SET_OVERLAY_PROP_DEFAULT(textures, PathUtils::resourcesUrl() + "images/whitePixel.png"); } { // Overlays did this conversion for rotation auto iter = overlayProps.find("rotation"); if (iter != overlayProps.end() && !overlayProps.contains("localRotation")) { QUuid parentID; { auto iter = overlayProps.find("parentID"); if (iter != overlayProps.end()) { parentID = iter.value().toUuid(); } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_PARENT_ID; parentID = DependencyManager::get()->getEntityProperties(id, desiredProperties).getParentID(); } } int parentJointIndex = -1; { auto iter = overlayProps.find("parentJointIndex"); if (iter != overlayProps.end()) { parentJointIndex = iter.value().toInt(); } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_PARENT_JOINT_INDEX; parentJointIndex = DependencyManager::get()->getEntityProperties(id, desiredProperties).getParentJointIndex(); } } glm::quat rotation = quatFromVariant(iter.value()); bool success = false; glm::quat localRotation = SpatiallyNestable::worldToLocal(rotation, parentID, parentJointIndex, false, success); if (success) { overlayProps["rotation"] = quatToVariant(localRotation); } } } if (type == "Text" || type == "Image" || type == "Grid" || type == "Web") { glm::quat originalRotation = ENTITY_ITEM_DEFAULT_ROTATION; { auto iter = overlayProps.find("rotation"); if (iter != overlayProps.end()) { originalRotation = quatFromVariant(iter.value()); } else { iter = overlayProps.find("localRotation"); if (iter != overlayProps.end()) { originalRotation = quatFromVariant(iter.value()); } else if (!add) { auto iter2 = savedRotations.find(id); if (iter2 != savedRotations.end()) { originalRotation = iter2.value(); } } } } if (!add) { savedRotations[id] = originalRotation; } else { rotationToSave = originalRotation; } glm::vec3 dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; { auto iter = overlayProps.find("dimensions"); if (iter != overlayProps.end()) { bool valid = false; dimensions = vec3FromVariant(iter.value(), valid); if (!valid) { dimensions = glm::vec3(vec2FromVariant(iter.value()), 0.0f); } } else if (!add) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_DIMENSIONS; dimensions = DependencyManager::get()->getEntityProperties(id, desiredProperties).getDimensions(); } } bool rotateX = dimensions.y < 0.0f; bool rotateY = dimensions.x < 0.0f; { glm::quat rotation = originalRotation; if (rotateX) { rotation = glm::angleAxis((float)M_PI, rotation * Vectors::RIGHT) * rotation; } if (rotateY) { rotation = glm::angleAxis((float)M_PI, rotation * Vectors::UP) * rotation; } overlayProps["localRotation"] = quatToVariant(rotation); overlayProps["dimensions"] = vec3toVariant(glm::abs(dimensions)); } } QScriptEngine scriptEngine; QScriptValue props = variantMapToScriptValue(overlayProps, scriptEngine); EntityItemProperties toReturn; EntityItemPropertiesFromScriptValueHonorReadOnly(props, toReturn); return toReturn; } QVariantMap Overlays::convertEntityToOverlayProperties(const EntityItemProperties& properties) { QScriptEngine scriptEngine; QVariantMap overlayProps = EntityItemPropertiesToScriptValue(&scriptEngine, properties).toVariant().toMap(); QString type = overlayProps["type"].toString(); overlayProps["type"] = entityToOverlayType(type); if (type != "PolyLine") { RENAME_PROP(position, p1); RENAME_PROP(position, start); } RENAME_PROP(position, point); if (type != "Model") { RENAME_PROP(dimensions, scale); } else { RENAME_PROP(modelScale, scale); } RENAME_PROP(dimensions, size); RENAME_PROP(ignorePickIntersection, ignoreRayIntersection); { RENAME_PROP_CONVERT(primitiveMode, isSolid, [](const QVariant& v) { return v.toString() == "solid" ? true : false; }); RENAME_PROP(isSolid, solid); RENAME_PROP(isSolid, isFilled); RENAME_PROP(isSolid, filled); RENAME_PROP_CONVERT(primitiveMode, isWire, [](const QVariant& v) { return v.toString() == "lines" ? true : false; }); RENAME_PROP(isWire, wire); } RENAME_PROP_CONVERT(renderLayer, drawInFront, [](const QVariant& v) { return v.toString() == "front" ? true : false; }); RENAME_PROP_CONVERT(renderLayer, drawHUDLayer, [](const QVariant& v) { return v.toString() == "hud" ? true : false; }); GROUP_ENTITY_TO_OVERLAY_PROP(grab, grabbable, grabbable); GROUP_ENTITY_TO_OVERLAY_PROP(pulse, min, pulseMin); GROUP_ENTITY_TO_OVERLAY_PROP(pulse, max, pulseMax); GROUP_ENTITY_TO_OVERLAY_PROP(pulse, period, pulsePeriod); GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(pulse, colorMode, colorPulse, [](const QVariant& v) { QString f = v.toString(); if (f == "in") { return 1.0f; } else if (f == "out") { return -1.0f; } return 0.0f; }); GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(pulse, alphaMode, alphaPulse, [](const QVariant& v) { QString f = v.toString(); if (f == "in") { return 1.0f; } else if (f == "out") { return -1.0f; } return 0.0f; }); if (type == "Model") { RENAME_PROP(modelURL, url); RENAME_PROP(animation, animationSettings); } else if (type == "Image") { RENAME_PROP(imageURL, url); } else if (type == "Text") { RENAME_PROP(textColor, color); } else if (type == "Web") { RENAME_PROP(sourceUrl, url); RENAME_PROP_CONVERT(inputMode, inputMode, [](const QVariant& v) { return v.toString() == "mouse" ? "Mouse" : "Touch"; }); } else if (type == "Gizmo") { RENAME_PROP_CONVERT(dimensions, outerRadius, [](const QVariant& v) { return 2.0f * vec3FromVariant(v).x; }); RENAME_PROP(outerRadius, radius); RENAME_PROP_CONVERT(rotation, rotation, [](const QVariant& v) { glm::quat rot = quatFromVariant(v); return quatToVariant(glm::angleAxis((float)M_PI_2, rot * Vectors::RIGHT) * rot); }); RENAME_PROP_CONVERT(localRotation, localRotation, [](const QVariant& v) { glm::quat rot = quatFromVariant(v); return quatToVariant(glm::angleAxis((float)M_PI_2, rot * Vectors::RIGHT) * rot); }); GROUP_ENTITY_TO_OVERLAY_PROP(ring, startAngle, startAt); GROUP_ENTITY_TO_OVERLAY_PROP(ring, endAngle, endAt); GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerRadius, innerRadius); GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerStartColor, innerStartColor); GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerEndColor, innerEndColor); GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerStartColor, outerStartColor); GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerEndColor, outerEndColor); GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerStartAlpha, innerStartAlpha); GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerEndAlpha, innerEndAlpha); GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerStartAlpha, outerStartAlpha); GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerEndAlpha, outerEndAlpha); GROUP_ENTITY_TO_OVERLAY_PROP(ring, hasTickMarks, hasTickMarks); GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksAngle, majorTickMarksAngle); GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksAngle, minorTickMarksAngle); GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksLength, majorTickMarksLength); GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksLength, minorTickMarksLength); GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksColor, majorTickMarksColor); GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksColor, minorTickMarksColor); } else if (type == "PolyLine") { QVector points = qVectorVec3FromScriptValue(scriptEngine.newVariant(overlayProps["linePoints"])); glm::vec3 position = vec3FromVariant(overlayProps["position"]); if (points.length() > 1) { overlayProps["p1"] = vec3toVariant(points[0] + position); overlayProps["p2"] = vec3toVariant(points[1] + position); overlayProps["localStart"] = vec3toVariant(points[0]); overlayProps["localEnd"] = vec3toVariant(points[1]); } RENAME_PROP(p1, startPoint); RENAME_PROP(p1, start); RENAME_PROP(p2, endPoint); RENAME_PROP(p2, end); QVector widths = qVectorFloatFromScriptValue(scriptEngine.newVariant(overlayProps["strokeWidths"])); if (widths.length() > 0) { overlayProps["lineWidth"] = widths[0]; } RENAME_PROP_CONVERT(glow, glow, [](const QVariant& v) { return v.toBool() ? 1.0f : 0.0f; }); } // Do at the end, in case this type was rotated above RENAME_PROP(rotation, orientation); RENAME_PROP(localRotation, localOrientation); return overlayProps; } QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; } 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) { #if !defined(DISABLE_QML) overlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); #endif } else if (type == TextOverlay::TYPE) { #if !defined(DISABLE_QML) overlay = Overlay::Pointer(new TextOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); #endif } else if (type == RectangleOverlay::TYPE) { overlay = Overlay::Pointer(new RectangleOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); } if (overlay) { overlay->setProperties(properties.toMap()); return add2DOverlay(overlay); } QString entityType = overlayToEntityType(type); if (entityType == "Unknown") { return UNKNOWN_ENTITY_ID; } QVariantMap propertyMap = properties.toMap(); if (type == "rectangle3d") { propertyMap["shape"] = "Quad"; } glm::quat rotationToSave; QUuid id = DependencyManager::get()->addEntityInternal(convertOverlayToEntityProperties(propertyMap, rotationToSave, entityType, true), entity::HostType::LOCAL); if (entityType == "Text" || entityType == "Image" || entityType == "Grid" || entityType == "Web") { savedRotations[id] = rotationToSave; } return 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; } 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; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { return add2DOverlay(Overlay::Pointer(overlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); } return DependencyManager::get()->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; } auto entityScriptingInterface = DependencyManager::get(); auto propertyMap = properties.toMap(); EntityItemProperties entityProperties = convertOverlayToEntityProperties(propertyMap, entityScriptingInterface->getEntityType(id), false, id); return !entityScriptingInterface->editEntity(id, entityProperties).isNull(); } bool Overlays::editOverlays(const QVariant& propertiesById) { if (_shuttingDown) { return false; } bool deferOverlays = QThread::currentThread() != thread(); QVariantMap deferred; const QVariantMap map = propertiesById.toMap(); auto entityScriptingInterface = DependencyManager::get(); 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 { auto propertyMap = properties.toMap(); entityScriptingInterface->editEntity(id, convertOverlayToEntityProperties(propertyMap, entityScriptingInterface->getEntityType(id), false, id)); } } // 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) { _overlaysToDelete.push_back(overlay); emit overlayDeleted(id); return; } DependencyManager::get()->deleteEntity(id); emit overlayDeleted(id); } QString Overlays::getOverlayType(const QUuid& id) { if (_shuttingDown) { return ""; } 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; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { return overlay->getType(); } return entityToOverlayType(DependencyManager::get()->getEntityType(id)); } QObject* Overlays::getOverlayObject(const QUuid& id) { 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; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { return qobject_cast(&(*overlay)); } return DependencyManager::get()->getEntityObject(id); } QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { if (_shuttingDown || !_enabled) { return UNKNOWN_ENTITY_ID; } QMutexLocker locker(&_mutex); QMapIterator i(_overlays); unsigned int bestStackOrder = 0; QUuid bestID = UNKNOWN_ENTITY_ID; while (i.hasNext()) { i.next(); auto thisOverlay = std::dynamic_pointer_cast(i.value()); if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() && thisOverlay->getBoundingRect().contains(point.x, point.y, false)) { if (thisOverlay->getStackOrder() > bestStackOrder) { bestID = i.key(); bestStackOrder = thisOverlay->getStackOrder(); } } } return bestID; } QVariant Overlays::getProperty(const QUuid& id, const QString& property) { Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (overlay->supportsGetProperty()) { return overlay->getProperty(property); } return QVariant(); } QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); auto propIter = overlayProperties.find(property); if (propIter != overlayProperties.end()) { return propIter.value(); } return QVariant(); } QVariantMap Overlays::getProperties(const QUuid& id, const QStringList& properties) { Overlay::Pointer overlay = get2DOverlay(id); QVariantMap result; if (overlay) { if (overlay->supportsGetProperty()) { for (const auto& property : properties) { result.insert(property, overlay->getProperty(property)); } } return result; } QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); for (const auto& property : properties) { auto propIter = overlayProperties.find(property); if (propIter != overlayProperties.end()) { result.insert(property, propIter.value()); } } return result; } QVariantMap Overlays::getOverlaysProperties(const QVariant& propertiesById) { QVariantMap map = propertiesById.toMap(); QVariantMap result; for (const auto& key : map.keys()) { result[key] = getProperties(QUuid(key), map[key].toStringList()); } return result; } RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& overlayIDsToInclude, const QScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { const QVector include = qVectorEntityItemIDFromScriptValue(overlayIDsToInclude); const QVector discard = qVectorEntityItemIDFromScriptValue(overlayIDsToDiscard); return findRayIntersectionVector(ray, precisionPicking, include, discard, visibleOnly, collidableOnly); } RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, const QVector& include, const QVector& 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()->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& include, const QVector& 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()->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; } QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); obj.setProperty("overlayID", overlayIDValue); obj.setProperty("distance", value.distance); obj.setProperty("face", boxFaceToString(value.face)); QScriptValue intersection = vec3ToScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); QScriptValue overlayIDValue = object.property("overlayID"); quuidFromScriptValue(overlayIDValue, value.overlayID); value.distance = object.property("distance").toVariant().toFloat(); value.face = boxFaceFromString(object.property("face").toVariant().toString()); QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } QScriptValue surfaceNormal = object.property("surfaceNormal"); if (surfaceNormal.isValid()) { vec3FromScriptValue(surfaceNormal, value.surfaceNormal); } value.extraInfo = object.property("extraInfo").toVariant().toMap(); } bool Overlays::isLoaded(const QUuid& id) { if (QThread::currentThread() != thread()) { bool result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "isLoaded", Q_RETURN_ARG(bool, result), Q_ARG(const QUuid&, id)); return result; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { return overlay->isLoaded(); } return DependencyManager::get()->isLoaded(id); } QSizeF Overlays::textSize(const QUuid& id, const QString& text) { if (QThread::currentThread() != thread()) { QSizeF result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(const QUuid&, id), Q_ARG(QString, text)); return result; } Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { if (auto textOverlay = std::dynamic_pointer_cast(overlay)) { return textOverlay->textSize(text); } return QSizeF(0.0f, 0.0f); } else { return DependencyManager::get()->textSize(id, text); } } bool Overlays::isAddedOverlay(const QUuid& id) { Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { return true; } return DependencyManager::get()->isAddedEntity(id); } void Overlays::sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event) { mousePressPointerEvent(id, event); } void Overlays::sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event) { mouseReleasePointerEvent(id, event); } void Overlays::sendMouseMoveOnOverlay(const QUuid& id, const PointerEvent& event) { mouseMovePointerEvent(id, event); } void Overlays::sendHoverEnterOverlay(const QUuid& id, const PointerEvent& event) { hoverEnterPointerEvent(id, event); } void Overlays::sendHoverOverOverlay(const QUuid& id, const PointerEvent& event) { hoverOverPointerEvent(id, event); } void Overlays::sendHoverLeaveOverlay(const QUuid& id, const PointerEvent& event) { hoverLeavePointerEvent(id, event); } float Overlays::width() { if (QThread::currentThread() != thread()) { float result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "width", Q_RETURN_ARG(float, result)); return result; } auto offscreenUi = DependencyManager::get(); return offscreenUi->getWindow()->size().width(); } float Overlays::height() { if (QThread::currentThread() != thread()) { float result; PROFILE_RANGE(script, __FUNCTION__); BLOCKING_INVOKE_METHOD(this, "height", Q_RETURN_ARG(float, result)); return result; } auto offscreenUi = DependencyManager::get(); return offscreenUi->getWindow()->size().height(); } 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; } } RayToOverlayIntersectionResult getPrevPickResult() { RayToOverlayIntersectionResult overlayResult; overlayResult.intersects = false; auto pickResult = DependencyManager::get()->getPrevPickResultTyped(DependencyManager::get()->getMouseRayPickID()); if (pickResult) { overlayResult.intersects = pickResult->type == IntersectionType::LOCAL_ENTITY; if (overlayResult.intersects) { overlayResult.intersection = pickResult->intersection; overlayResult.distance = pickResult->distance; overlayResult.surfaceNormal = pickResult->surfaceNormal; overlayResult.overlayID = pickResult->objectID; overlayResult.extraInfo = pickResult->extraInfo; } } return overlayResult; } PointerEvent Overlays::calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType) { glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(id, rayPickResult.intersection); return PointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); } void Overlays::hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { emit hoverEnterOverlay(id, event); } } void Overlays::hoverOverPointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { emit hoverOverOverlay(id, event); } } void Overlays::hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { emit hoverLeaveOverlay(id, event); } } std::pair Overlays::mousePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mousePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; PointerEvent pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); mousePressPointerEvent(_currentClickingOnOverlayID, pointerEvent); return { rayPickResult.distance, rayPickResult.overlayID }; } emit mousePressOffOverlay(); return { FLT_MAX, UNKNOWN_ENTITY_ID }; } void Overlays::mousePressPointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { emit mousePressOnOverlay(id, event); } } bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; auto pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); return true; } emit mouseDoublePressOffOverlay(); return false; } bool Overlays::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); if (rayPickResult.intersects) { auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release); mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent); } _currentClickingOnOverlayID = UNKNOWN_ENTITY_ID; return false; } void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { emit mouseReleaseOnOverlay(id, event); } } bool Overlays::mouseMoveEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); if (rayPickResult.intersects) { auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move); mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent); // If previously hovering over a different overlay then leave hover on that overlay. if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) { auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); } // If hovering over a new overlay then enter hover on that overlay. if (rayPickResult.overlayID != _currentHoverOverOverlayID) { hoverEnterPointerEvent(rayPickResult.overlayID, pointerEvent); } // Hover over current overlay. hoverOverPointerEvent(rayPickResult.overlayID, pointerEvent); _currentHoverOverOverlayID = rayPickResult.overlayID; } else { // If previously hovering an overlay then leave hover. if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID) { auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); _currentHoverOverOverlayID = UNKNOWN_ENTITY_ID; } } return false; } void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { emit mouseMoveOnOverlay(id, event); } } QVector Overlays::findOverlays(const glm::vec3& center, float radius) { PROFILE_RANGE(script_entities, __FUNCTION__); QVector result; auto entityTree = DependencyManager::get()->getEntityTree(); if (entityTree) { unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); // For legacy reasons, this only finds visible objects searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); entityTree->withReadLock([&] { entityTree->evalEntitiesInSphere(center, radius, PickFilter(searchFilter), result); }); } return result; } /**jsdoc *

An overlay may be one of the following types:

* * * * * * * * * * * * * * * * * * * * *
Value2D/3DDescription
image2DAn image. Synonym: billboard.
rectangle2DA rectangle.
text2DText.
cube3DA cube. Can also use a shape overlay to create a cube.
sphere3DA sphere. Can also use a shape overlay to create a sphere.
rectangle3d3DA rectangle.
shape3DA geometric shape, such as a cube, sphere, or cylinder.
model3DA model.
text3d3DText.
image3d3DAn image.
web3d3DWeb content.
line3d3DA line.
grid3DA grid of lines in a plane.
circle3d3DA circle.
*

2D overlays are rendered on the display surface in desktop mode and on the HUD surface in HMD mode. 3D overlays are * rendered at a position and orientation in-world, but are deprecated (use local entities instead).

*

Each overlay type has different {@link Overlays.OverlayProperties|OverlayProperties}.

* @typedef {string} Overlays.OverlayType */ /**jsdoc * Different overlay types have different properties: some common to all overlays (listed below) and some specific to each * {@link Overlays.OverlayType|OverlayType} (linked to below). The properties are accessed as an object of property names and * values. 3D Overlays are local entities, internally, so they also support the corresponding entity properties. * * @typedef {object} Overlays.OverlayProperties * @property {Uuid} id - The ID of the overlay. Read-only. * @property {Overlays.OverlayType} type - The overlay type. Read-only. * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. * * @see The different entity types have additional properties as follows: * @see {@link Overlays.OverlayProperties-Image|OverlayProperties-Image} * @see {@link Overlays.OverlayProperties-Text|OverlayProperties-Text} * @see {@link Overlays.OverlayProperties-Rectangle|OverlayProperties-Rectangle} * @see {@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube} * @see {@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere} * @see {@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D} * @see {@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape} * @see {@link Overlays.OverlayProperties-Model|OverlayProperties-Model} * @see {@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D} * @see {@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D} * @see {@link Overlays.OverlayProperties-Web|OverlayProperties-Web} * @see {@link Overlays.OverlayProperties-Line|OverlayProperties-Line} * @see {@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid} * @see {@link Overlays.OverlayProperties-Circle|OverlayProperties-Circle} */ /**jsdoc * The "Image" {@link Overlays.OverlayType|OverlayType} is a 2D image. * @typedef {object} Overlays.OverlayProperties-Image * @property {Rect} bounds - The position and size of the image display area, in pixels. Write-only. * @property {number} x - Integer left, x-coordinate value of the image display area = bounds.x. * Write-only. * @property {number} y - Integer top, y-coordinate value of the image display area = bounds.y. * Write-only. * @property {number} width - Integer width of the image display area = bounds.width. Write-only. * @property {number} height - Integer height of the image display area = bounds.height. Write-only. * @property {string} imageURL - The URL of the image file to display. The image is scaled to fit to the bounds. * Write-only. * @property {Vec2} subImage=0,0 - Integer coordinates of the top left pixel to start using image content from. * Write-only. * @property {Color} color=0,0,0 - The color to apply over the top of the image to colorize it. Write-only. * @property {number} alpha=0.0 - The opacity of the color applied over the top of the image, 0.0 - * 1.0. Write-only. */ /**jsdoc * The "Text" {@link Overlays.OverlayType|OverlayType} is for 2D text. * @typedef {object} Overlays.OverlayProperties-Text * @property {Rect} bounds - The position and size of the rectangle, in pixels. Write-only. * @property {number} x - Integer left, x-coordinate value = bounds.x. Write-only. * @property {number} y - Integer top, y-coordinate value = bounds.y. Write-only. * @property {number} width - Integer width of the rectangle = bounds.width. Write-only. * @property {number} height - Integer height of the rectangle = bounds.height. Write-only. * * @property {number} margin=0 - Sets the leftMargin and topMargin values, in pixels. * Write-only. * @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin. * Write-only. * @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin. * Write-only. * @property {string} text="" - The text to display. Text does not automatically wrap; use \n for a line break. Text * is clipped to the bounds. Write-only. * @property {number} font.size=18 - The size of the text, in pixels. Write-only. * @property {number} lineHeight=18 - The height of a line of text, in pixels. Write-only. * @property {Color} color=255,255,255 - The color of the text. Synonym: textColor. Write-only. * @property {number} alpha=1.0 - The opacity of the overlay, 0.0 - 1.0. Write-only. * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. Write-only. * @property {number} backgroundAlpha=0.7 - The opacity of the background rectangle. Write-only. */ /**jsdoc * The "Rectangle" {@link Overlays.OverlayType|OverlayType} is for 2D rectangles. * @typedef {object} Overlays.OverlayProperties-Rectangle * @property {Rect} bounds - The position and size of the rectangle, in pixels. Write-only. * @property {number} x - Integer left, x-coordinate value = bounds.x. Write-only. * @property {number} y - Integer top, y-coordinate value = bounds.y. Write-only. * @property {number} width - Integer width of the rectangle = bounds.width. Write-only. * @property {number} height - Integer height of the rectangle = bounds.height. Write-only. * * @property {Color} color=0,0,0 - The color of the overlay. Write-only. * @property {number} alpha=1.0 - The opacity of the overlay, 0.0 - 1.0. Write-only. * @property {number} borderWidth=1 - Integer width of the border, in pixels. The border is drawn within the rectangle's bounds. * It is not drawn unless either borderColor or borderAlpha are specified. Write-only. * @property {number} radius=0 - Integer corner radius, in pixels. Write-only. * @property {Color} borderColor=0,0,0 - The color of the border. Write-only. * @property {number} borderAlpha=1.0 - The opacity of the border, 0.0 - 1.0. * Write-only. */ /**jsdoc * The "Cube" {@link Overlays.OverlayType|OverlayType} is for 3D cubes. * @typedef {object} Overlays.OverlayProperties-Cube * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * */ /**jsdoc * The "Sphere" {@link Overlays.OverlayType|OverlayType} is for 3D spheres. * @typedef {object} Overlays.OverlayProperties-Sphere * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * */ /**jsdoc * The "Rectangle3D" {@link Overlays.OverlayType|OverlayType} is for 3D rectangles. * @typedef {object} Overlays.OverlayProperties-Rectangle3D * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * */ /**jsdoc *

A shape {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical shapes:

* * * * * * * * * * * * * * * * * * * * * *
ValueDimensionsDescription
"Circle"2DA circle oriented in 3D.
"Cone"3D
"Cube"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Line"1DA line oriented in 3D.
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
* @typedef {string} Overlays.Shape */ /**jsdoc * The "Shape" {@link Overlays.OverlayType|OverlayType} is for 3D shapes. * @typedef {object} Overlays.OverlayProperties-Shape * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay. */ /**jsdoc * The "Model" {@link Overlays.OverlayType|OverlayType} is for 3D models. * @typedef {object} Overlays.OverlayProperties-Model * @property {string} name - The name of the overlay. * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: size. * @property {Vec3} scale - The scale factor applied to the model's dimensions. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {string} url - The URL of the FBX or OBJ model used for the overlay. * @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load * first. * @property {object.} textures - Maps the named textures in the model to the JPG or PNG images in the urls. * @property {string[]} jointNames - The names of the joints - if any - in the model. Read-only. * @property {Quat[]} jointRotations - The relative rotations of the model's joints. * @property {Vec3[]} jointTranslations - The relative translations of the model's joints. * @property {Quat[]} jointOrientations - The absolute orientations of the model's joints, in world coordinates. Read-only. * @property {Vec3[]} jointPositions - The absolute positions of the model's joints, in world coordinates. Read-only. * @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play. * @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at. * @property {number} animationSettings.firstFrame=0 - The frame to start playing at. * @property {number} animationSettings.lastFrame=0 - The frame to finish playing at. * @property {number} animationSettings.currentFrame=0 - The current frame being played. * @property {boolean} animationSettings.running=false - Whether or not the animation is playing. * @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop. * @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and * translations of the last frame played should be maintained. * @property {boolean} animationSettings.allowTranslation=false - Whether or not translations contained in the animation should * be played. */ /**jsdoc * The "Text3D" {@link Overlays.OverlayType|OverlayType} is for 3D text. * @typedef {object} Overlays.OverlayProperties-Text3D * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {boolean} isFacingAvatar - If true< / code>, the overlay is rotated to face the user's camera about an axis * parallel to the user's avatar's "up" direction. * @property {string} text="" - The text to display.Text does not automatically wrap; use \n< / code> for a line break. * @property {number} textAlpha=1 - The text alpha value. * @property {Color} backgroundColor=0,0,0 - The background color. * @property {number} backgroundAlpha=0.7 - The background alpha value. * @property {number} lineHeight=1 - The height of a line of text in meters. * @property {number} leftMargin=0.1 - The left margin, in meters. * @property {number} topMargin=0.1 - The top margin, in meters. * @property {number} rightMargin=0.1 - The right margin, in meters. * @property {number} bottomMargin=0.1 - The bottom margin, in meters. */ /**jsdoc * The "Image3D" {@link Overlays.OverlayType|OverlayType} is for 3D images. * @typedef {object} Overlays.OverlayProperties-Image3D * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis * parallel to the user's avatar's "up" direction. * @property {string} url - The URL of the PNG or JPG image to display. * @property {Rect} subImage - The portion of the image to display. Defaults to the full image. * @property {boolean} emissive - If true, the overlay is displayed at full brightness, otherwise it is rendered * with scene lighting. * @property {bool} keepAspectRatio=true - overlays will maintain the aspect ratio when the subImage is applied. */ /**jsdoc * The "Web" {@link Overlays.OverlayType|OverlayType} is for 3D web surfaces. * @typedef {object} Overlays.OverlayProperties-Web * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis * parallel to the user's avatar's "up" direction. * @property {string} url - The URL of the Web page to display. * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. * @property {string} inputMode=Touch - The user input mode to use - either "Touch" or "Mouse". */ /**jsdoc * The "Line" {@link Overlays.OverlayType|OverlayType} is for 3D lines. * @typedef {object} Overlays.OverlayProperties-Line * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {Uuid} endParentID=null - The avatar, entity, or overlay that the end point of the line is parented to. * @property {number} endParentJointIndex=65535 - Integer value specifying the skeleton joint that the end point of the line is * attached to if parentID is an avatar skeleton. A value of 65535 means "no joint". * @property {Vec3} start - The start point of the line. Synonyms: startPoint and p1. * @property {Vec3} end - The end point of the line. Synonyms: endPoint and p2. * @property {Vec3} localStart - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as start. Synonym: localPosition. * @property {Vec3} localEnd - The local position of the overlay relative to its parent if the overlay has a * endParentID set, otherwise the same value as end. * @property {number} length - The length of the line, in meters. This can be set after creating a line with start and end * points. * @property {number} glow=0 - If glow > 0, the line is rendered with a glow. * @property {number} lineWidth=0.02 - Width of the line, in meters. */ /**jsdoc * The "Grid" {@link Overlays.OverlayType|OverlayType} is for 3D grid. * @typedef {object} Overlays.OverlayProperties-Grid * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {boolean} followCamera=true - If true, the grid is always visible even as the camera moves to another position. * @property {number} majorGridEvery=5 - Integer number of minorGridEvery intervals at which to draw a thick grid line. Minimum value = 1. * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = 0.001. */ /**jsdoc * The "Circle" {@link Overlays.OverlayType|OverlayType} is for 3D circle. * @typedef {object} Overlays.OverlayProperties-Circle * @property {string} name - The name of the overlay. * @property {Color} color=255,255,255 - The color of the overlay. * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. Synonym: localOrientation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at, in degrees. * @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at, in degrees. * @property {number} outerRadius = 1 - The outer radius of the overlay, in meters.Synonym: radius< / code>. * @property {number} innerRadius = 0 - The inner radius of the overlay, in meters. * @property {Color} color = 255, 255, 255 - The color of the overlay.Setting this value also sets the values of * innerStartColor< / code>, innerEndColor< / code>, outerStartColor< / code>, and outerEndColor< / code>. * @property {Color} startColor - Sets the values of innerStartColor< / code> and outerStartColor< / code>. * Write - only.< / em> * @property {Color} endColor - Sets the values of innerEndColor< / code> and outerEndColor< / code>. * Write - only.< / em> * @property {Color} innerColor - Sets the values of innerStartColor< / code> and innerEndColor< / code>. * Write - only.< / em> * @property {Color} outerColor - Sets the values of outerStartColor< / code> and outerEndColor< / code>. * Write - only.< / em> * @property {Color} innerStartcolor - The color at the inner start point of the overlay. * @property {Color} innerEndColor - The color at the inner end point of the overlay. * @property {Color} outerStartColor - The color at the outer start point of the overlay. * @property {Color} outerEndColor - The color at the outer end point of the overlay. * @property {number} alpha = 0.5 - The opacity of the overlay, 0.0< / code> -1.0< / code>.Setting this value also sets * the values of innerStartAlpha< / code>, innerEndAlpha< / code>, outerStartAlpha< / code>, and * outerEndAlpha< / code>.Synonym: Alpha< / code>; write - only< / em>. * @property {number} startAlpha - Sets the values of innerStartAlpha< / code> and outerStartAlpha< / code>. * Write - only.< / em> * @property {number} endAlpha - Sets the values of innerEndAlpha< / code> and outerEndAlpha< / code>. * Write - only.< / em> * @property {number} innerAlpha - Sets the values of innerStartAlpha< / code> and innerEndAlpha< / code>. * Write - only.< / em> * @property {number} outerAlpha - Sets the values of outerStartAlpha< / code> and outerEndAlpha< / code>. * Write - only.< / em> * @property {number} innerStartAlpha = 0 - The alpha at the inner start point of the overlay. * @property {number} innerEndAlpha = 0 - The alpha at the inner end point of the overlay. * @property {number} outerStartAlpha = 0 - The alpha at the outer start point of the overlay. * @property {number} outerEndAlpha = 0 - The alpha at the outer end point of the overlay. * * @property {boolean} hasTickMarks = false - If true< / code>, tick marks are drawn. * @property {number} majorTickMarksAngle = 0 - The angle between major tick marks, in degrees. * @property {number} minorTickMarksAngle = 0 - The angle between minor tick marks, in degrees. * @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters.A positive value draws tick marks * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. * @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters.A positive value draws tick marks * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. * @property {Color} majorTickMarksColor = 0, 0, 0 - The color of the major tick marks. * @property {Color} minorTickMarksColor = 0, 0, 0 - The color of the minor tick marks. */