// // LaserPointer.cpp // interface/src/raypick // // Created by Sam Gondelman 7/11/2017 // Copyright 2017 High Fidelity, Inc. // Copyright 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 // #include "LaserPointer.h" #include "Application.h" #include "avatar/AvatarManager.h" #include #include "PickManager.h" #include "RayPick.h" #include "PolyLineEntityItem.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithParent, bool enabled) : PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime, centerEndY, lockEnd, distanceScaleEnd, scaleWithParent, enabled) { } PickQuery::PickType LaserPointer::getType() const { return PickQuery::PickType::Ray; } void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { auto renderState = std::static_pointer_cast(_renderStates[state]); if (renderState) { updateRenderState(renderState->getPathID(), pathProps); QVariantMap pathPropsMap = pathProps.toMap(); QVariant lineWidth = pathPropsMap["lineWidth"]; if (lineWidth.isValid()) { renderState->setLineWidth(lineWidth.toFloat()); } if (pathPropsMap.contains("linePoints")) { QVariantList linePoints = pathPropsMap["linePoints"].toList(); renderState->setNumPoints(linePoints.length()); } } } PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto rayPickResult = std::dynamic_pointer_cast(pickResult); if (!rayPickResult) { return std::make_shared(); } return std::make_shared(*rayPickResult.get()); } QVariantMap LaserPointer::toVariantMap() const { QVariantMap qVariantMap = Parent::toVariantMap(); QVariantMap qRenderStates; for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { auto renderState = iter->second; QVariantMap qRenderState; qRenderState["start"] = renderState->getStartID(); qRenderState["path"] = std::static_pointer_cast(renderState)->getPathID(); qRenderState["end"] = renderState->getEndID(); qRenderStates[iter->first.c_str()] = qRenderState; } qVariantMap["renderStates"] = qRenderStates; QVariantMap qDefaultRenderStates; for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { float distance = iter->second.first; auto defaultRenderState = iter->second.second; QVariantMap qDefaultRenderState; qDefaultRenderState["distance"] = distance; qDefaultRenderState["start"] = defaultRenderState->getStartID(); qDefaultRenderState["path"] = std::static_pointer_cast(defaultRenderState)->getPathID(); qDefaultRenderState["end"] = defaultRenderState->getEndID(); qDefaultRenderStates[iter->first.c_str()] = qDefaultRenderState; } qVariantMap["defaultRenderStates"] = qDefaultRenderStates; return qVariantMap; } glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); } glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { auto rayPickResult = std::static_pointer_cast(pickResult); if (!rayPickResult) { return glm::vec3(0.0f); } if (distance > 0.0f) { PickRay pick = PickRay(rayPickResult->pickVariant); return pick.origin + distance * pick.direction; } else { return rayPickResult->intersection; } } glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f)); } IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? rayPickResult->type : IntersectionType::NONE); } QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? rayPickResult->objectID : QUuid()); } void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { auto rayPickResult = std::static_pointer_cast(pickResult); if (rayPickResult) { rayPickResult->type = type; rayPickResult->objectID = id; rayPickResult->intersection = intersection; rayPickResult->distance = distance; rayPickResult->surfaceNormal = surfaceNormal; rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal); } } LaserPointer::RenderState::RenderState(const QUuid& startID, const QUuid& pathID, const QUuid& endID) : StartEndRenderState(startID, endID), _pathID(pathID) { if (!getPathID().isNull()) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_IGNORE_PICK_INTERSECTION; desiredProperties += PROP_LINE_POINTS; desiredProperties += PROP_STROKE_WIDTHS; auto properties = DependencyManager::get()->getEntityPropertiesInternal(getPathID(), desiredProperties, false); auto widths = properties.getStrokeWidths(); _lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0]; setNumPoints(properties.getLinePoints().length()); _pathIgnorePicks = properties.getIgnorePickIntersection(); } } void LaserPointer::RenderState::cleanup() { StartEndRenderState::cleanup(); if (!getPathID().isNull()) { DependencyManager::get()->deleteEntity(getPathID()); } } void LaserPointer::RenderState::disable() { StartEndRenderState::disable(); if (!getPathID().isNull()) { EntityItemProperties properties; properties.setVisible(false); properties.setIgnorePickIntersection(true); DependencyManager::get()->editEntity(getPathID(), properties); } } void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { StartEndRenderState::update(origin, end, surfaceNormal, parentScale, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); if (!getPathID().isNull()) { EntityItemProperties properties; QVector points; const size_t numPoints = getNumPoints(); points.append(glm::vec3(0.0f)); const glm::vec3 endPoint = end - origin; if (numPoints > 2) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_VISIBLE; auto oldProperties = DependencyManager::get()->getEntityPropertiesInternal(getPathID(), desiredProperties, false); bool hasUnmodifiedEndPoint = false; glm::vec3 unmodifiedEndPoint; float directionLength = glm::length(endPoint); auto rayPickResult = std::static_pointer_cast(pickResult); if (rayPickResult && rayPickResult->pickVariant.contains("unmodifiedDirection")) { unmodifiedEndPoint = directionLength * vec3FromVariant(rayPickResult->pickVariant["unmodifiedDirection"]); hasUnmodifiedEndPoint = true; } // Segment points are evenly spaced between origin and end for (size_t i = 1; i < numPoints - 1; i++) { const float frac = ((float)i / (numPoints - 1)); if (!oldProperties.getVisible() || !_hasSetLinePoints || !hasUnmodifiedEndPoint) { points.append(frac * endPoint); } else { points.append(frac * directionLength * glm::normalize(glm::mix(unmodifiedEndPoint, endPoint, frac))); } } _hasSetLinePoints = true; } points.append(endPoint); properties.setPosition(origin); properties.setRotation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)); properties.setLinePoints(points); properties.setVisible(true); properties.setIgnorePickIntersection(doesPathIgnorePicks()); QVector normals; normals.fill(glm::vec3(0.0f, 0.0f, 1.0f), (int)numPoints); properties.setNormals(normals); QVector widths; float width = getLineWidth() * parentScale; widths.fill(width, (int)numPoints); properties.setStrokeWidths(widths); DependencyManager::get()->editEntity(getPathID(), properties); } } std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap, const QList &entityProperties) { QUuid startID; if (propMap["startPropertyIndex"].isValid()) { int startPropertyIndex = propMap["startPropertyIndex"].toInt(); if (startPropertyIndex >= 0 && startPropertyIndex < entityProperties.length()) { EntityItemProperties startProperties(entityProperties[startPropertyIndex]); startProperties.getGrab().setGrabbable(false); startID = DependencyManager::get()->addEntityInternal(startProperties, entity::HostType::LOCAL); } } QUuid pathID; if (propMap["pathPropertyIndex"].isValid()) { int pathPropertyIndex = propMap["pathPropertyIndex"].toInt(); if (pathPropertyIndex >= 0 && pathPropertyIndex < entityProperties.length()) { EntityItemProperties pathProperties(entityProperties[pathPropertyIndex]); // laser paths must be PolyLine pathProperties.setType(EntityTypes::EntityType::PolyLine); pathProperties.getGrab().setGrabbable(false); pathID = DependencyManager::get()->addEntityInternal(pathProperties, entity::HostType::LOCAL); } } QUuid endID; if (propMap["endPropertyIndex"].isValid()) { int endPropertyIndex = propMap["endPropertyIndex"].toInt(); if (endPropertyIndex >= 0 && endPropertyIndex < entityProperties.length()) { EntityItemProperties endProperties(entityProperties[endPropertyIndex]); endProperties.getGrab().setGrabbable(false); endID = DependencyManager::get()->addEntityInternal(endProperties, entity::HostType::LOCAL); } } return std::make_shared(startID, pathID, endID); } PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { QUuid pickedID; glm::vec3 intersection, surfaceNormal, direction, origin; auto rayPickResult = std::static_pointer_cast(pickResult); if (rayPickResult) { intersection = rayPickResult->intersection; surfaceNormal = rayPickResult->surfaceNormal; const QVariantMap& searchRay = rayPickResult->pickVariant; direction = vec3FromVariant(searchRay["direction"]); origin = vec3FromVariant(searchRay["origin"]); pickedID = rayPickResult->objectID; } if (pickedID != target.objectID) { intersection = findIntersection(target, origin, direction); } glm::vec2 pos2D = findPos2D(target, intersection); // If we just started triggering and we haven't moved too much, don't update intersection and pos2D TriggerState& state = hover ? _latestState : _states[button]; auto avatar = DependencyManager::get()->getMyAvatar(); if (avatar) { float sensorToWorldScale = avatar->getSensorToWorldScale(); float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale; bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared; if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) { pos2D = state.triggerPos2D; intersection = state.intersection; surfaceNormal = state.surfaceNormal; } if (!withinDeadspot) { state.deadspotExpired = true; } } return PointerEvent(pos2D, intersection, surfaceNormal, direction); } glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { case ENTITY: case LOCAL_ENTITY: return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); default: return glm::vec3(NAN); } }