402 lines · 17.3 KB · C++
RawDownload
////  Created by Sam Gondelman 7/17/2018//  Copyright 2018 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//  SPDX-License-Identifier: Apache-2.0//#include "PathPointer.h" #include "Application.h"#include "avatar/AvatarManager.h" #include <DependencyManager.h>#include <PickManager.h>#include <Quat.h>#include "PickScriptingInterface.h"#include "RayPick.h" PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,                         bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd,                         bool distanceScaleEnd, bool scaleWithParent, bool enabled) :    Pointer(DependencyManager::get<PickScriptingInterface>()->createPick(type, rayProps), enabled, hover),    _renderStates(renderStates),    _defaultRenderStates(defaultRenderStates),    _triggers(triggers),    _faceAvatar(faceAvatar),    _followNormal(followNormal),    _followNormalStrength(followNormalStrength),    _centerEndY(centerEndY),    _lockEnd(lockEnd),    _distanceScaleEnd(distanceScaleEnd),    _scaleWithParent(scaleWithParent){    for (auto& state : _renderStates) {        if (!enabled || state.first != _currentRenderState) {            state.second->disable();        }    }    for (auto& state : _defaultRenderStates) {        if (!enabled || state.first != _currentRenderState) {            state.second.second->disable();        }    }} PathPointer::~PathPointer() {    for (auto& renderState : _renderStates) {        renderState.second->cleanup();    }    for (auto& renderState : _defaultRenderStates) {        renderState.second.second->cleanup();    }} void PathPointer::setRenderState(const std::string& state) {    withWriteLock([&] {        if (!_currentRenderState.empty() && state != _currentRenderState) {            auto renderState = _renderStates.find(_currentRenderState);            if (renderState != _renderStates.end()) {                renderState->second->disable();            }            auto defaultRenderState = _defaultRenderStates.find(_currentRenderState);            if (defaultRenderState != _defaultRenderStates.end()) {                defaultRenderState->second.second->disable();            }        }        _currentRenderState = state;    });} void PathPointer::setLength(float length) {    withWriteLock([&] {        _pathLength = length;    });} void PathPointer::setLockEndUUID(const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat) {    withWriteLock([&] {        _lockEndObject.id = objectID;        _lockEndObject.isAvatar = isAvatar;        _lockEndObject.offsetMat = offsetMat;    });} PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) {    PickResultPointer visualPickResult = pickResult;    glm::vec3 origin = getPickOrigin(pickResult);    IntersectionType type = getPickedObjectType(pickResult);    QUuid id;    glm::vec3 intersection;    float distance;    glm::vec3 surfaceNormal;     if (type != IntersectionType::HUD) {        glm::vec3 endVec;        if (!_lockEndObject.id.isNull()) {            glm::vec3 pos;            glm::quat rot;            glm::vec3 dim;            glm::vec3 registrationPoint;            // TODO: use isAvatar            {                EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);                glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());                glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;                pos = extractTranslation(finalPosAndRotMat);                rot = props.getRotation();                dim = props.getDimensions();                registrationPoint = props.getRegistrationPoint();            }            const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);            endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));            glm::vec3 direction = endVec - origin;            distance = glm::distance(origin, endVec);            glm::vec3 normalizedDirection = glm::normalize(direction);             type = IntersectionType::ENTITY;            id = _lockEndObject.id;            intersection = endVec;            surfaceNormal = -normalizedDirection;            setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);        } else if (type != IntersectionType::NONE && _lockEnd) {            id = getPickedObjectID(pickResult);            if (type == IntersectionType::ENTITY) {                endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(id)[3];            } else if (type == IntersectionType::AVATAR) {                endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(id)->getPosition();            }            glm::vec3 direction = endVec - origin;            distance = glm::distance(origin, endVec);            glm::vec3 normalizedDirection = glm::normalize(direction);            intersection = endVec;            surfaceNormal = -normalizedDirection;            setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);        }    }    return visualPickResult;} void PathPointer::updateVisuals(const PickResultPointer& pickResult) {    IntersectionType type = getPickedObjectType(pickResult);    auto renderState = _renderStates.find(_currentRenderState);    auto defaultRenderState = _defaultRenderStates.find(_currentRenderState);    float parentScale = 1.0f;    if (_enabled && _scaleWithParent) {        glm::vec3 dimensions = DependencyManager::get<PickManager>()->getParentTransform(_pickUID).getScale();        parentScale = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z);    }     if (_enabled && !_currentRenderState.empty() && renderState != _renderStates.end() &&        (type != IntersectionType::NONE || _pathLength > 0.0f)) {        glm::vec3 origin = getPickOrigin(pickResult);        glm::vec3 end = getPickEnd(pickResult, _pathLength);        glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult);        renderState->second->update(origin, end, surfaceNormal, parentScale, _distanceScaleEnd, _centerEndY, _faceAvatar,                                    _followNormal, _followNormalStrength, _pathLength, pickResult);        if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) {            defaultRenderState->second.second->disable();        }    } else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) {        if (renderState != _renderStates.end() && renderState->second->isEnabled()) {            renderState->second->disable();        }        glm::vec3 origin = getPickOrigin(pickResult);        glm::vec3 end = getPickEnd(pickResult, defaultRenderState->second.first);        defaultRenderState->second.second->update(origin, end, Vectors::UP, parentScale, _distanceScaleEnd, _centerEndY,                                                  _faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first,                                                  pickResult);    } else if (!_currentRenderState.empty()) {        if (renderState != _renderStates.end() && renderState->second->isEnabled()) {            renderState->second->disable();        }        if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) {            defaultRenderState->second.second->disable();        }    }} void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {    withWriteLock([&] {        auto renderState = _renderStates.find(state);        if (renderState != _renderStates.end()) {            updateRenderState(renderState->second->getStartID(), startProps);            updateRenderState(renderState->second->getEndID(), endProps);            QVariant startDim = startProps.toMap()["dimensions"];            if (startDim.isValid()) {                renderState->second->setStartDim(vec3FromVariant(startDim));            }            QVariant endDim = endProps.toMap()["dimensions"];            if (endDim.isValid()) {                renderState->second->setEndDim(vec3FromVariant(endDim));            }            QVariant rotation = endProps.toMap()["rotation"];            if (rotation.isValid()) {                renderState->second->setEndRot(quatFromVariant(rotation));            }             editRenderStatePath(state, pathProps);        }    });} void PathPointer::updateRenderState(const QUuid& id, const QVariant& props) {    // FIXME: updating render state props no longer works since removing 3D Overlays    //if (!id.isNull() && props.isValid()) {    //    QVariantMap propMap = props.toMap();    //    propMap.remove("visible");    //    qApp->getOverlays().editOverlay(id, propMap);    //}} Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) {    return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));} glm::vec2 PathPointer::getScroll(const PickResultPointer& pickResult) {    bool isActive = true;    glm::vec2 accum { 0.0f };     for (const PointerTrigger& trigger : _triggers) {        std::string button = trigger.getButton();         // if ScrollActive is available, use it to lock out accidental scrolls        // (like on the UI, where the lasers are always active even though they're invisible)        if (button == "ScrollActive" && trigger.getEndpoint()->peek().value < 0.1f) {            isActive = false;        } else if (button == "ScrollX") {            accum.x += trigger.getEndpoint()->peek().value;        } else if (button == "ScrollY") {            accum.y += trigger.getEndpoint()->peek().value;        }    }     return isActive ? accum : glm::vec2(0.0f);} Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) {    std::unordered_set<std::string> toReturn;     for (const PointerTrigger& trigger : _triggers) {        std::string button = trigger.getButton();        TriggerState& state = _states[button];        // TODO: right now, LaserPointers don't support axes, only on/off buttons        if (trigger.getEndpoint()->peek().value >= 1.0f) {            toReturn.insert(button);             if (_previousButtons.find(button) == _previousButtons.end()) {                // start triggering for buttons that were just pressed                state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));                state.intersection = getPickEnd(pickResult);                state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection);                state.triggerStartTime = usecTimestampNow();                state.surfaceNormal = getPickedObjectNormal(pickResult);                state.deadspotExpired = false;                state.wasTriggering = true;                state.triggering = true;                _latestState = state;            }        } else {            // stop triggering for buttons that aren't pressed            state.wasTriggering = state.triggering;            state.triggering = false;            _latestState = state;        }    }    _previousButtons = toReturn;    return toReturn;} StartEndRenderState::StartEndRenderState(const QUuid& startID, const QUuid& endID) :    _startID(startID), _endID(endID) {    auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();    if (!_startID.isNull()) {        EntityPropertyFlags desiredProperties;        desiredProperties += PROP_DIMENSIONS;        desiredProperties += PROP_IGNORE_PICK_INTERSECTION;        auto properties = entityScriptingInterface->getEntityPropertiesInternal(_startID, desiredProperties, false);        _startDim = properties.getDimensions();        _startIgnorePicks = properties.getIgnorePickIntersection();    }    if (!_endID.isNull()) {        EntityPropertyFlags desiredProperties;        desiredProperties += PROP_DIMENSIONS;        desiredProperties += PROP_ROTATION;        desiredProperties += PROP_IGNORE_PICK_INTERSECTION;        auto properties = entityScriptingInterface->getEntityPropertiesInternal(_endID, desiredProperties, false);        _endDim = properties.getDimensions();        _endRot = properties.getRotation();        _endIgnorePicks = properties.getIgnorePickIntersection();    }} void StartEndRenderState::cleanup() {    auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();    if (!_startID.isNull()) {        entityScriptingInterface->deleteEntity(_startID);    }    if (!_endID.isNull()) {        entityScriptingInterface->deleteEntity(_endID);    }} void StartEndRenderState::disable() {    auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();    if (!getStartID().isNull()) {        EntityItemProperties properties;        properties.setVisible(false);        properties.setIgnorePickIntersection(true);        entityScriptingInterface->editEntity(getStartID(), properties);    }    if (!getEndID().isNull()) {        EntityItemProperties properties;        properties.setVisible(false);        properties.setIgnorePickIntersection(true);        entityScriptingInterface->editEntity(getEndID(), properties);    }    _enabled = false;} void StartEndRenderState::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) {    auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();    if (!getStartID().isNull()) {        EntityItemProperties properties;        properties.setPosition(origin);        properties.setVisible(true);        properties.setDimensions(getStartDim() * parentScale);        properties.setIgnorePickIntersection(doesStartIgnorePicks());        entityScriptingInterface->editEntity(getStartID(), properties);    }     if (!getEndID().isNull()) {        EntityItemProperties properties;        EntityPropertyFlags desiredProperties;        desiredProperties += PROP_DIMENSIONS;         glm::vec3 dim;        if (distanceScaleEnd) {            glm::vec3 camPos = qApp->getCamera().getPosition();            dim = getEndDim() * std::pow(glm::distance(camPos, end), 0.75f);        } else {            dim = getEndDim() * parentScale;        }        properties.setDimensions(dim);         glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal);        normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0));        glm::vec3 avatarUp = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;        glm::quat rotation = glm::rotation(Vectors::UP, avatarUp);        glm::vec3 position = end;        if (!centerEndY) {            if (followNormal) {                position = end + 0.5f * dim.y * surfaceNormal;            } else {                position = end + 0.5f * dim.y * avatarUp;            }        }        if (faceAvatar) {            glm::quat orientation = followNormal ? normalQuat : DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation();            glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition(), surfaceNormal);            glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld;            glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel);            glm::quat lookAtFlatWorld = orientation * lookAtFlatModel;            rotation = lookAtFlatWorld;        } else if (followNormal) {            rotation = normalQuat;        }        if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) {            if (!_avgEndRotInitialized) {                _avgEndRot = rotation;                _avgEndRotInitialized = true;            } else {                rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength);                if (!centerEndY) {                    position = end + 0.5f * dim.y * (rotation * Vectors::UP);                }                _avgEndRot = rotation;            }        }        properties.setPosition(position);        properties.setRotation(rotation);        properties.setVisible(true);        properties.setIgnorePickIntersection(doesEndIgnorePicks());        entityScriptingInterface->editEntity(getEndID(), properties);    }    _enabled = true;} glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {    switch (pickedObject.type) {    case ENTITY:    case LOCAL_ENTITY:        return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);    case HUD:        return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);    default:        return glm::vec2(NAN);    }}