261 lines · 10.7 KB · C++
RawDownload
////  Created by Bradley Austin Davis on 2017/10/24//  Copyright 2013-2017 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 "StylusPointer.h" #include "RayPick.h" #include "Application.h"#include "avatar/AvatarManager.h"#include "avatar/MyAvatar.h" #include <DependencyManager.h>#include "PickScriptingInterface.h"#include <PickManager.h> static const float TABLET_MIN_HOVER_DISTANCE = -0.1f;static const float TABLET_MAX_HOVER_DISTANCE = 0.1f;static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f;static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f; static const float HOVER_HYSTERESIS = 0.01f;static const float TOUCH_HYSTERESIS = 0.001f; static const QString DEFAULT_STYLUS_MODEL_URL = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx"; StylusPointer::StylusPointer(const QVariant& props, const QUuid& stylus, bool hover, bool enabled,                             const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions) :    Pointer(DependencyManager::get<PickScriptingInterface>()->createPick(PickQuery::PickType::Stylus, props), enabled, hover),    _stylus(stylus),    _modelPositionOffset(modelPositionOffset),    _modelDimensions(modelDimensions),    _modelRotationOffset(modelRotationOffset){} StylusPointer::~StylusPointer() {    if (!_stylus.isNull()) {        DependencyManager::get<EntityScriptingInterface>()->deleteEntity(_stylus);    }} PickQuery::PickType StylusPointer::getType() const {    return PickQuery::PickType::Stylus;} QUuid StylusPointer::buildStylus(const QVariantMap& properties) {    EntityItemProperties entityProperties;    QString modelURL = DEFAULT_STYLUS_MODEL_URL;     if (properties["model"].isValid()) {        QVariantMap modelData = properties["model"].toMap();         if (modelData["url"].isValid()) {            modelURL = modelData["url"].toString();        }    }    // TODO: make these configurable per pointer    entityProperties.setType(EntityTypes::Model);    entityProperties.setName("stylus");    entityProperties.setModelURL(modelURL);    entityProperties.setPrimitiveMode(PrimitiveMode::SOLID);    entityProperties.setVisible(true);    entityProperties.setIgnorePickIntersection(true);    return DependencyManager::get<EntityScriptingInterface>()->addEntityInternal(entityProperties, entity::HostType::LOCAL);} void StylusPointer::updateVisuals(const PickResultPointer& pickResult) {    auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);     if (_enabled && !qApp->getPreferAvatarFingerOverStylus() && _renderState != DISABLED && stylusPickResult) {        StylusTip tip(stylusPickResult->pickVariant);        if (tip.side != bilateral::Side::Invalid) {            show(tip);            return;        }    }    if (_showing) {        hide();    }} void StylusPointer::show(const StylusTip& tip) {    if (!_stylus.isNull()) {        auto modelOrientation = tip.orientation * _modelRotationOffset;        auto sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();        auto modelPositionOffset = modelOrientation * (_modelPositionOffset * sensorToWorldScale);        EntityItemProperties properties;        properties.setPosition(tip.position + modelPositionOffset);        properties.setRotation(modelOrientation);        properties.setDimensions(sensorToWorldScale * _modelDimensions);        properties.setVisible(true);        DependencyManager::get<EntityScriptingInterface>()->editEntity(_stylus, properties);    }    _showing = true;} void StylusPointer::hide() {    if (!_stylus.isNull()) {        EntityItemProperties properties;        properties.setVisible(false);        DependencyManager::get<EntityScriptingInterface>()->editEntity(_stylus, properties);    }    _showing = false;} bool StylusPointer::shouldHover(const PickResultPointer& pickResult) {    auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);    if (_renderState == EVENTS_ON && stylusPickResult && stylusPickResult->intersects) {        auto sensorScaleFactor = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();        float hysteresis = _state.hovering ? HOVER_HYSTERESIS * sensorScaleFactor : 0.0f;        bool hovering = isWithinBounds(stylusPickResult->distance, TABLET_MIN_HOVER_DISTANCE * sensorScaleFactor,                                       TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor, hysteresis);         _state.hovering = hovering;        return hovering;    }     _state.hovering = false;    return false;} bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {    auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);    bool wasTriggering = false;    if (_renderState == EVENTS_ON && stylusPickResult) {        auto sensorScaleFactor = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();        float distance = stylusPickResult->distance;         // If we're triggering on an object, recalculate the distance instead of using the pickResult        glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]);        glm::vec3 direction = _state.triggering ? -_state.surfaceNormal : -stylusPickResult->surfaceNormal;        if ((_state.triggering || _state.wasTriggering) && stylusPickResult->objectID != _state.triggeredObject.objectID) {            distance = glm::dot(findIntersection(_state.triggeredObject, origin, direction) - origin, direction);        }         float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f;        if (isWithinBounds(distance, TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor,            TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) {            _state.wasTriggering = _state.triggering;            if (!_state.triggering) {                _state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type);                _state.intersection = stylusPickResult->intersection;                _state.triggerPos2D = findPos2D(_state.triggeredObject, origin);                _state.triggerStartTime = usecTimestampNow();                _state.surfaceNormal = stylusPickResult->surfaceNormal;                _state.deadspotExpired = false;                _state.triggering = true;            }            return true;        }        wasTriggering = _state.triggering;    }     _state.wasTriggering = wasTriggering;    _state.triggering = false;    return false;} PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const {    auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult);    if (!stylusPickResult) {        return std::make_shared<StylusPickResult>();    }    return std::make_shared<StylusPickResult>(*stylusPickResult.get());} Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) {    auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);    if (!stylusPickResult) {        return PickedObject();    }    return PickedObject(stylusPickResult->objectID, stylusPickResult->type);} glm::vec2 StylusPointer::getScroll(const PickResultPointer& pickResult) {    glm::vec2 accum { 0.0f };    // TODO: thumbstick scrolling with stylus? probably not needed    return accum;} Pointer::Buttons StylusPointer::getPressedButtons(const PickResultPointer& pickResult) {    // TODO: custom buttons for styluses    Pointer::Buttons toReturn({ "Primary", "Focus" });    return toReturn;} PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {    QUuid pickedID;    glm::vec2 pos2D;    glm::vec3 intersection, surfaceNormal, direction, origin;    auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult);    if (stylusPickResult) {        intersection = stylusPickResult->intersection;        surfaceNormal = hover ? stylusPickResult->surfaceNormal : _state.surfaceNormal;        const QVariantMap& stylusTip = stylusPickResult->pickVariant;        origin = vec3FromVariant(stylusTip["position"]);        direction = -surfaceNormal;        pos2D = findPos2D(target, origin);        pickedID = stylusPickResult->objectID;    }     // If we just started triggering and we haven't moved too much, don't update intersection and pos2D    float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->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;    } else if (pickedID != target.objectID) {        intersection = findIntersection(target, origin, direction);    }    if (!withinDeadspot) {        _state.deadspotExpired = true;    }     return PointerEvent(pos2D, intersection, surfaceNormal, direction);} bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) {    return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis));} void StylusPointer::setRenderState(const std::string& state) {    if (state == "events on") {        _renderState = EVENTS_ON;    } else if (state == "events off") {        _renderState = EVENTS_OFF;    } else if (state == "disabled") {        _renderState = DISABLED;    }} QVariantMap StylusPointer::toVariantMap() const {    return Parent::toVariantMap();} glm::vec3 StylusPointer::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);    }} glm::vec2 StylusPointer::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);    }}